r)return}}if(i&&i!=this.groupTouchParams.group){var l=e.get(i.groupId),h=e.get(this.groupTouchParams.group.groupId);h&&l&&(this.options.groupOrderSwap(h,l,e),e.update(h),e.update(l));var u=e.getIds({order:this.options.groupOrder});if(!wE.equalArray(u,this.groupTouchParams.originalOrder))for(var d=this.groupTouchParams.originalOrder,c=this.groupTouchParams.group.groupId,p=Math.min(d.length,u.length),f=0,m=0,v=0;f=p)break;if(u[f+m]==c)m=1;else if(d[f+v]==c)v=1;else{var g=av(u).call(u,d[f+v]),y=e.get(u[f+m]),b=e.get(d[f+v]);this.options.groupOrderSwap(y,b,e),e.update(y),e.update(b);var _=u[f+m];u[f+m]=d[f+v],u[g]=_,f++}}}}}},{key:"_onGroupDragEnd",value:function(t){if(this.groupTouchParams.isDragging=!1,this.options.groupEditable.order&&this.groupTouchParams.group){t.stopPropagation();var e=this,i=e.groupTouchParams.group.groupId,n=e.groupsData.getDataSet(),r=wE.extend({},n.get(i));e.options.onMoveGroup(r,(function(t){if(t)t[n._idProp]=i,n.update(t);else{var r=n.getIds({order:e.options.groupOrder});if(!wE.equalArray(r,e.groupTouchParams.originalOrder))for(var o=e.groupTouchParams.originalOrder,s=Math.min(o.length,r.length),a=0;a=s)break;var l=av(r).call(r,o[a]),h=n.get(r[a]),u=n.get(o[a]);e.options.groupOrderSwap(h,u,n),n.update(h),n.update(u);var d=r[a];r[a]=o[a],r[l]=d,a++}}})),e.body.emitter.emit("groupDragged",{groupId:i}),this.toggleGroupDragClassName(this.groupTouchParams.group),this.groupTouchParams.group=null}}},{key:"_onSelectItem",value:function(t){if(this.options.selectable){var e=t.srcEvent&&(t.srcEvent.ctrlKey||t.srcEvent.metaKey),i=t.srcEvent&&t.srcEvent.shiftKey;if(e||i)this._onMultiSelectItem(t);else{var n=this.getSelection(),r=this.itemFromTarget(t),o=r&&r.selectable?[r.id]:[];this.setSelection(o);var s=this.getSelection();(s.length>0||n.length>0)&&this.body.emitter.emit("select",{items:s,event:t})}}}},{key:"_onMouseOver",value:function(t){var e=this.itemFromTarget(t);if(e&&e!==this.itemFromRelatedTarget(t)){var i=e.getTitle();if(this.options.showTooltips&&i){null==this.popup&&(this.popup=new IA(this.body.dom.root,this.options.tooltip.overflowMethod||"flip")),this.popup.setText(i);var n=this.body.dom.centerContainer,r=n.getBoundingClientRect();this.popup.setPosition(t.clientX-r.left+n.offsetLeft,t.clientY-r.top+n.offsetTop),this.setPopupTimer(this.popup)}else this.clearPopupTimer(),null!=this.popup&&this.popup.hide();this.body.emitter.emit("itemover",{item:e.id,event:t})}}},{key:"_onMouseOut",value:function(t){var e=this.itemFromTarget(t);e&&(e!==this.itemFromRelatedTarget(t)&&(this.clearPopupTimer(),null!=this.popup&&this.popup.hide(),this.body.emitter.emit("itemout",{item:e.id,event:t})))}},{key:"_onMouseMove",value:function(t){if(this.itemFromTarget(t)&&(null!=this.popupTimer&&this.setPopupTimer(this.popup),this.options.showTooltips&&this.options.tooltip.followMouse&&this.popup&&!this.popup.hidden)){var e=this.body.dom.centerContainer,i=e.getBoundingClientRect();this.popup.setPosition(t.clientX-i.left+e.offsetLeft,t.clientY-i.top+e.offsetTop),this.popup.show()}}},{key:"_onMouseWheel",value:function(t){this.touchParams.itemIsDragging&&this._onDragEnd(t)}},{key:"_onUpdateItem",value:function(t){if(this.options.selectable&&(this.options.editable.updateTime||this.options.editable.updateGroup)){var e=this;if(t){var i=e.itemsData.get(t.id);this.options.onUpdate(i,(function(t){t&&e.itemsData.update(t)}))}}}},{key:"_onDropObjectOnItem",value:function(t){var e=this.itemFromTarget(t),i=JSON.parse(t.dataTransfer.getData("text"));this.options.onDropObjectOnItem(i,e)}},{key:"_onAddItem",value:function(t){if(this.options.selectable&&this.options.editable.add){var e,i,n=this,r=this.options.snap||null,o=this.dom.frame.getBoundingClientRect(),s=this.options.rtl?o.right-t.center.x:t.center.x-o.left,a=this.body.util.toTime(s),l=this.body.util.getScale(),h=this.body.util.getStep();"drop"==t.type?((i=JSON.parse(t.dataTransfer.getData("text"))).content=i.content?i.content:"new item",i.start=i.start?i.start:r?r(a,l,h):a,i.type=i.type||"box",i[this.itemsData.idProp]=i.id||UM(),"range"!=i.type||i.end||(e=this.body.util.toTime(s+this.props.width/5),i.end=r?r(e,l,h):e)):((i={start:r?r(a,l,h):a,content:"new item"})[this.itemsData.idProp]=UM(),"range"===this.options.type&&(e=this.body.util.toTime(s+this.props.width/5),i.end=r?r(e,l,h):e));var u=this.groupFromTarget(t);u&&(i.group=u.groupId),i=this._cloneItemData(i),this.options.onAdd(i,(function(e){e&&(n.itemsData.add(e),"drop"==t.type&&n.setSelection([e.id]))}))}}},{key:"_onMultiSelectItem",value:function(t){var e=this;if(this.options.selectable){var n=this.itemFromTarget(t);if(n){var r=this.options.multiselect?this.getSelection():[];if((t.srcEvent&&t.srcEvent.shiftKey||!1||this.options.sequentialSelection)&&this.options.multiselect){var o=this.itemsData.get(n.id).group,s=void 0;this.options.multiselectPerGroup&&r.length>0&&(s=this.itemsData.get(r[0]).group),this.options.multiselectPerGroup&&null!=s&&s!=o||r.push(n.id);var a=i._getItemRange(this.itemsData.get(r));if(!this.options.multiselectPerGroup||s==o)for(var l in r=[],this.items)if(this.items.hasOwnProperty(l)){var h=this.items[l],u=h.data.start,d=void 0!==h.data.end?h.data.end:u;!(u>=a.min&&d<=a.max)||this.options.multiselectPerGroup&&s!=this.itemsData.get(h.id).group||h instanceof AA||r.push(h.id)}}else{var c=av(r).call(r,n.id);-1==c?r.push(n.id):_f(r).call(r,c,1)}var p=mm(r).call(r,(function(t){return e.getItemById(t).selectable}));this.setSelection(p),this.body.emitter.emit("select",{items:this.getSelection(),event:t})}}}},{key:"itemFromElement",value:function(t){for(var e=t;e;){if(e.hasOwnProperty("vis-item"))return e["vis-item"];e=e.parentNode}return null}},{key:"itemFromTarget",value:function(t){return this.itemFromElement(t.target)}},{key:"itemFromRelatedTarget",value:function(t){return this.itemFromElement(t.relatedTarget)}},{key:"groupFromTarget",value:function(t){var e=t.center?t.center.y:t.clientY,i=this.groupIds;i.length<=0&&this.groupsData&&(i=this.groupsData.getIds({order:this.options.groupOrder}));for(var n=0;n=a.top&&ea.top)return o}else if(0===n&&ee)&&(e=t.end):(null==e||t.start>e)&&(e=t.start)})),{min:i,max:e}}},{key:"itemSetFromTarget",value:function(t){for(var e=t.target;e;){if(e.hasOwnProperty("vis-itemset"))return e["vis-itemset"];e=e.parentNode}return null}}]),i}(IE);tI.types={background:AA,box:CA,range:EA,point:MA},tI.prototype._onAdd=tI.prototype._onUpdate;var eI,iI=!1,nI="background: #FFeeee; color: #dd0000",rI=function(){function t(){Ma(this,t)}return Yd(t,null,[{key:"validate",value:function(e,i,n){iI=!1,eI=i;var r=i;return void 0!==n&&(r=i[n]),t.parse(e,r,[]),iI}},{key:"parse",value:function(e,i,n){for(var r in e)e.hasOwnProperty(r)&&t.check(r,e,i,n)}},{key:"check",value:function(e,i,n,r){if(void 0!==n[e]||void 0!==n.__any__){var o=e,s=!0;void 0===n[e]&&void 0!==n.__any__&&(o="__any__",s="object"===t.getType(i[e]));var a=n[o];s&&void 0!==a.__type__&&(a=a.__type__),t.checkFields(e,i,n,o,a,r)}else t.getSuggestion(e,n,r)}},{key:"checkFields",value:function(e,i,n,r,o,s){var a=function(i){console.log("%c"+i+t.printLocation(s,e),nI)},l=t.getType(i[e]),h=o[l];void 0!==h?"array"===t.getType(h)&&-1===av(h).call(h,i[e])?(a('Invalid option detected in "'+e+'". Allowed values are:'+t.print(h)+' not "'+i[e]+'". '),iI=!0):"object"===l&&"__any__"!==r&&(s=wE.copyAndExtendArray(s,e),t.parse(i[e],n[r],s)):void 0===o.any&&(a('Invalid type received for "'+e+'". Expected: '+t.print(rp(o))+". Received ["+l+'] "'+i[e]+'"'),iI=!0)}},{key:"getType",value:function(t){var e=Nd(t);return"object"===e?null===t?"null":t instanceof Boolean?"boolean":t instanceof Number?"number":t instanceof String?"string":qc(t)?"array":t instanceof Date?"date":void 0!==t.nodeType?"dom":!0===t._isAMomentObject?"moment":"object":"number"===e?"number":"boolean"===e?"boolean":"string"===e?"string":void 0===e?"undefined":e}},{key:"getSuggestion",value:function(e,i,n){var r,o=t.findInOptions(e,i,n,!1),s=t.findInOptions(e,eI,[],!0);r=void 0!==o.indexMatch?" in "+t.printLocation(o.path,e,"")+'Perhaps it was incomplete? Did you mean: "'+o.indexMatch+'"?\n\n':s.distance<=4&&o.distance>s.distance?" in "+t.printLocation(o.path,e,"")+"Perhaps it was misplaced? Matching option found at: "+t.printLocation(s.path,s.closestMatch,""):o.distance<=8?'. Did you mean "'+o.closestMatch+'"?'+t.printLocation(o.path,e):". Did you mean one of these: "+t.print(rp(i))+t.printLocation(n,e),console.log('%cUnknown option detected: "'+e+'"'+r,nI),iI=!0}},{key:"findInOptions",value:function(e,i,n){var r=arguments.length>3&&void 0!==arguments[3]&&arguments[3],o=1e9,s="",a=[],l=e.toLowerCase(),h=void 0;for(var u in i){var d=void 0;if(void 0!==i[u].__type__&&!0===r){var c=t.findInOptions(e,i[u],wE.copyAndExtendArray(n,u));o>c.distance&&(s=c.closestMatch,a=c.path,o=c.distance,h=c.indexMatch)}else{var p;-1!==av(p=u.toLowerCase()).call(p,l)&&(h=u),o>(d=t.levenshteinDistance(e,u))&&(s=u,a=wE.copyArray(n),o=d)}}return{closestMatch:s,path:a,distance:o,indexMatch:h}}},{key:"printLocation",value:function(t,e){for(var i="\n\n"+(arguments.length>2&&void 0!==arguments[2]?arguments[2]:"Problem value found at: \n")+"options = {\n",n=0;n0&&void 0!==arguments[0]?arguments[0]:1;Ma(this,t),this.pixelRatio=e,this.generated=!1,this.centerCoordinates={x:144.5,y:144.5},this.r=289*.49,this.color={r:255,g:255,b:255,a:1},this.hueCircle=void 0,this.initialColor={r:255,g:255,b:255,a:1},this.previousColor=void 0,this.applied=!1,this.updateCallback=function(){},this.closeCallback=function(){},this._create()}return Yd(t,[{key:"insertTo",value:function(t){void 0!==this.hammer&&(this.hammer.destroy(),this.hammer=void 0),this.container=t,this.container.appendChild(this.frame),this._bindHammer(),this._setSize()}},{key:"setUpdateCallback",value:function(t){if("function"!=typeof t)throw new Error("Function attempted to set as colorPicker update callback is not a function.");this.updateCallback=t}},{key:"setCloseCallback",value:function(t){if("function"!=typeof t)throw new Error("Function attempted to set as colorPicker closing callback is not a function.");this.closeCallback=t}},{key:"_isColorString",value:function(t){if("string"==typeof t)return fI[t]}},{key:"setColor",value:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1];if("none"!==t){var i,n=this._isColorString(t);if(void 0!==n&&(t=n),!0===wE.isString(t)){if(!0===wE.isValidRGB(t)){var r=t.substr(4).substr(0,t.length-5).split(",");i={r:r[0],g:r[1],b:r[2],a:1}}else if(!0===wE.isValidRGBA(t)){var o=t.substr(5).substr(0,t.length-6).split(",");i={r:o[0],g:o[1],b:o[2],a:o[3]}}else if(!0===wE.isValidHex(t)){var s=wE.hexToRGB(t);i={r:s.r,g:s.g,b:s.b,a:1}}}else if(t instanceof Object&&void 0!==t.r&&void 0!==t.g&&void 0!==t.b){var a=void 0!==t.a?t.a:"1.0";i={r:t.r,g:t.g,b:t.b,a:a}}if(void 0===i)throw new Error("Unknown color passed to the colorPicker. Supported are strings: rgb, hex, rgba. Object: rgb ({r:r,g:g,b:b,[a:a]}). Supplied: "+vv(t));this._setColor(i,e)}}},{key:"show",value:function(){void 0!==this.closeCallback&&(this.closeCallback(),this.closeCallback=void 0),this.applied=!1,this.frame.style.display="block",this._generateHueCircle()}},{key:"_hide",value:function(){var t=this;!0===(!(arguments.length>0&&void 0!==arguments[0])||arguments[0])&&(this.previousColor=wE.extend({},this.color)),!0===this.applied&&this.updateCallback(this.initialColor),this.frame.style.display="none",Rv((function(){void 0!==t.closeCallback&&(t.closeCallback(),t.closeCallback=void 0)}),0)}},{key:"_save",value:function(){this.updateCallback(this.color),this.applied=!1,this._hide()}},{key:"_apply",value:function(){this.applied=!0,this.updateCallback(this.color),this._updatePicker(this.color)}},{key:"_loadLast",value:function(){void 0!==this.previousColor?this.setColor(this.previousColor,!1):alert("There is no last color to load...")}},{key:"_setColor",value:function(t){!0===(!(arguments.length>1&&void 0!==arguments[1])||arguments[1])&&(this.initialColor=wE.extend({},t)),this.color=t;var e=wE.RGBToHSV(t.r,t.g,t.b),i=2*Math.PI,n=this.r*e.s,r=this.centerCoordinates.x+n*Math.sin(i*e.h),o=this.centerCoordinates.y+n*Math.cos(i*e.h);this.colorPickerSelector.style.left=r-.5*this.colorPickerSelector.clientWidth+"px",this.colorPickerSelector.style.top=o-.5*this.colorPickerSelector.clientHeight+"px",this._updatePicker(t)}},{key:"_setOpacity",value:function(t){this.color.a=t/100,this._updatePicker(this.color)}},{key:"_setBrightness",value:function(t){var e=wE.RGBToHSV(this.color.r,this.color.g,this.color.b);e.v=t/100;var i=wE.HSVToRGB(e.h,e.s,e.v);i.a=this.color.a,this.color=i,this._updatePicker()}},{key:"_updatePicker",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.color,e=wE.RGBToHSV(t.r,t.g,t.b),i=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1)),i.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var n=this.colorPickerCanvas.clientWidth,r=this.colorPickerCanvas.clientHeight;i.clearRect(0,0,n,r),i.putImageData(this.hueCircle,0,0),i.fillStyle="rgba(0,0,0,"+(1-e.v)+")",i.circle(this.centerCoordinates.x,this.centerCoordinates.y,this.r),Vv(i).call(i),this.brightnessRange.value=100*e.v,this.opacityRange.value=100*t.a,this.initialColorDiv.style.backgroundColor="rgba("+this.initialColor.r+","+this.initialColor.g+","+this.initialColor.b+","+this.initialColor.a+")",this.newColorDiv.style.backgroundColor="rgba("+this.color.r+","+this.color.g+","+this.color.b+","+this.color.a+")"}},{key:"_setSize",value:function(){this.colorPickerCanvas.style.width="100%",this.colorPickerCanvas.style.height="100%",this.colorPickerCanvas.width=289*this.pixelRatio,this.colorPickerCanvas.height=289*this.pixelRatio}},{key:"_create",value:function(){var t,e,i,n;if(this.frame=document.createElement("div"),this.frame.className="vis-color-picker",this.colorPickerDiv=document.createElement("div"),this.colorPickerSelector=document.createElement("div"),this.colorPickerSelector.className="vis-selector",this.colorPickerDiv.appendChild(this.colorPickerSelector),this.colorPickerCanvas=document.createElement("canvas"),this.colorPickerDiv.appendChild(this.colorPickerCanvas),this.colorPickerCanvas.getContext){var r=this.colorPickerCanvas.getContext("2d");this.pixelRatio=(window.devicePixelRatio||1)/(r.webkitBackingStorePixelRatio||r.mozBackingStorePixelRatio||r.msBackingStorePixelRatio||r.oBackingStorePixelRatio||r.backingStorePixelRatio||1),this.colorPickerCanvas.getContext("2d").setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0)}else{var o=document.createElement("DIV");o.style.color="red",o.style.fontWeight="bold",o.style.padding="10px",o.innerHTML="Error: your browser does not support HTML canvas",this.colorPickerCanvas.appendChild(o)}this.colorPickerDiv.className="vis-color",this.opacityDiv=document.createElement("div"),this.opacityDiv.className="vis-opacity",this.brightnessDiv=document.createElement("div"),this.brightnessDiv.className="vis-brightness",this.arrowDiv=document.createElement("div"),this.arrowDiv.className="vis-arrow",this.opacityRange=document.createElement("input");try{this.opacityRange.type="range",this.opacityRange.min="0",this.opacityRange.max="100"}catch(t){}this.opacityRange.value="100",this.opacityRange.className="vis-range",this.brightnessRange=document.createElement("input");try{this.brightnessRange.type="range",this.brightnessRange.min="0",this.brightnessRange.max="100"}catch(t){}this.brightnessRange.value="100",this.brightnessRange.className="vis-range",this.opacityDiv.appendChild(this.opacityRange),this.brightnessDiv.appendChild(this.brightnessRange);var s=this;this.opacityRange.onchange=function(){s._setOpacity(this.value)},this.opacityRange.oninput=function(){s._setOpacity(this.value)},this.brightnessRange.onchange=function(){s._setBrightness(this.value)},this.brightnessRange.oninput=function(){s._setBrightness(this.value)},this.brightnessLabel=document.createElement("div"),this.brightnessLabel.className="vis-label vis-brightness",this.brightnessLabel.innerHTML="brightness:",this.opacityLabel=document.createElement("div"),this.opacityLabel.className="vis-label vis-opacity",this.opacityLabel.innerHTML="opacity:",this.newColorDiv=document.createElement("div"),this.newColorDiv.className="vis-new-color",this.newColorDiv.innerHTML="new",this.initialColorDiv=document.createElement("div"),this.initialColorDiv.className="vis-initial-color",this.initialColorDiv.innerHTML="initial",this.cancelButton=document.createElement("div"),this.cancelButton.className="vis-button vis-cancel",this.cancelButton.innerHTML="cancel",this.cancelButton.onclick=Tp(t=this._hide).call(t,this,!1),this.applyButton=document.createElement("div"),this.applyButton.className="vis-button vis-apply",this.applyButton.innerHTML="apply",this.applyButton.onclick=Tp(e=this._apply).call(e,this),this.saveButton=document.createElement("div"),this.saveButton.className="vis-button vis-save",this.saveButton.innerHTML="save",this.saveButton.onclick=Tp(i=this._save).call(i,this),this.loadButton=document.createElement("div"),this.loadButton.className="vis-button vis-load",this.loadButton.innerHTML="load last",this.loadButton.onclick=Tp(n=this._loadLast).call(n,this),this.frame.appendChild(this.colorPickerDiv),this.frame.appendChild(this.arrowDiv),this.frame.appendChild(this.brightnessLabel),this.frame.appendChild(this.brightnessDiv),this.frame.appendChild(this.opacityLabel),this.frame.appendChild(this.opacityDiv),this.frame.appendChild(this.newColorDiv),this.frame.appendChild(this.initialColorDiv),this.frame.appendChild(this.cancelButton),this.frame.appendChild(this.applyButton),this.frame.appendChild(this.saveButton),this.frame.appendChild(this.loadButton)}},{key:"_bindHammer",value:function(){var t=this;this.drag={},this.pinch={},this.hammer=new uP(this.colorPickerCanvas),this.hammer.get("pinch").set({enable:!0}),dP(this.hammer,(function(e){t._moveSelector(e)})),this.hammer.on("tap",(function(e){t._moveSelector(e)})),this.hammer.on("panstart",(function(e){t._moveSelector(e)})),this.hammer.on("panmove",(function(e){t._moveSelector(e)})),this.hammer.on("panend",(function(e){t._moveSelector(e)}))}},{key:"_generateHueCircle",value:function(){if(!1===this.generated){var t=this.colorPickerCanvas.getContext("2d");void 0===this.pixelRation&&(this.pixelRatio=(window.devicePixelRatio||1)/(t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1)),t.setTransform(this.pixelRatio,0,0,this.pixelRatio,0,0);var e,i,n,r,o=this.colorPickerCanvas.clientWidth,s=this.colorPickerCanvas.clientHeight;t.clearRect(0,0,o,s),this.centerCoordinates={x:.5*o,y:.5*s},this.r=.49*o;var a,l=2*Math.PI/360,h=1/this.r;for(n=0;n<360;n++)for(r=0;r3&&void 0!==arguments[3]?arguments[3]:1;Ma(this,t),this.parent=e,this.changedOptions=[],this.container=i,this.allowCreation=!1,this.options={},this.initialized=!1,this.popupCounter=0,this.defaultOptions={enabled:!1,filter:!0,container:void 0,showButton:!0},wE.extend(this.options,this.defaultOptions),this.configureOptions=n,this.moduleOptions={},this.domElements=[],this.popupDiv={},this.popupLimit=5,this.popupHistory={},this.colorPicker=new mI(r),this.wrapper=void 0}return Yd(t,[{key:"setOptions",value:function(t){if(void 0!==t){this.popupHistory={},this._removePopup();var e=!0;if("string"==typeof t)this.options.filter=t;else if(qc(t))this.options.filter=t.join();else if("object"===Nd(t)){if(null==t)throw new TypeError("options cannot be null");void 0!==t.container&&(this.options.container=t.container),void 0!==mm(t)&&(this.options.filter=mm(t)),void 0!==t.showButton&&(this.options.showButton=t.showButton),void 0!==t.enabled&&(e=t.enabled)}else"boolean"==typeof t?(this.options.filter=!0,e=t):"function"==typeof t&&(this.options.filter=t,e=!0);!1===mm(this.options)&&(e=!1),this.options.enabled=e}this._clean()}},{key:"setModuleOptions",value:function(t){this.moduleOptions=t,!0===this.options.enabled&&(this._clean(),void 0!==this.options.container&&(this.container=this.options.container),this._create())}},{key:"_create",value:function(){this._clean(),this.changedOptions=[];var t=mm(this.options),e=0,i=!1;for(var n in this.configureOptions)this.configureOptions.hasOwnProperty(n)&&(this.allowCreation=!1,i=!1,"function"==typeof t?i=(i=t(n,[]))||this._handleObject(this.configureOptions[n],[n],!0):!0!==t&&-1===av(t).call(t,n)||(i=!0),!1!==i&&(this.allowCreation=!0,e>0&&this._makeItem([]),this._makeHeader(n),this._handleObject(this.configureOptions[n],[n])),e++);this._makeButton(),this._push()}},{key:"_push",value:function(){this.wrapper=document.createElement("div"),this.wrapper.className="vis-configuration-wrapper",this.container.appendChild(this.wrapper);for(var t=0;t1?i-1:0),r=1;r2&&void 0!==arguments[2]&&arguments[2],n=document.createElement("div");return n.className="vis-configuration vis-config-label vis-config-s"+e.length,n.innerHTML=!0===i?wE.xss(""+t+":"):wE.xss(t+":"),n}},{key:"_makeDropdown",value:function(t,e,i){var n=document.createElement("select");n.className="vis-configuration vis-config-select";var r=0;void 0!==e&&-1!==av(t).call(t,e)&&(r=av(t).call(t,e));for(var o=0;oo&&1!==o&&(a.max=Math.ceil(e*u),h=a.max,l="range increased"),a.value=e}else a.value=n;var d=document.createElement("input");d.className="vis-configuration vis-config-rangeinput",d.value=Number(a.value);var c=this;a.onchange=function(){d.value=this.value,c._update(Number(this.value),i)},a.oninput=function(){d.value=this.value};var p=this._makeLabel(i[i.length-1],i),f=this._makeItem(i,p,a,d);""!==l&&this.popupHistory[f]!==h&&(this.popupHistory[f]=h,this._setupPopup(l,f))}},{key:"_makeButton",value:function(){var t=this;if(!0===this.options.showButton){var e=document.createElement("div");e.className="vis-configuration vis-config-button",e.innerHTML="generate options",e.onclick=function(){t._printOptions()},e.onmouseover=function(){e.className="vis-configuration vis-config-button hover"},e.onmouseout=function(){e.className="vis-configuration vis-config-button"},this.optionsContainer=document.createElement("div"),this.optionsContainer.className="vis-configuration vis-config-option-container",this.domElements.push(this.optionsContainer),this.domElements.push(e)}}},{key:"_setupPopup",value:function(t,e){var i=this;if(!0===this.initialized&&!0===this.allowCreation&&this.popupCounter1&&void 0!==arguments[1]?arguments[1]:[],i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=!1,r=mm(this.options),o=!1;for(var s in t)if(t.hasOwnProperty(s)){n=!0;var a=t[s],l=wE.copyAndExtendArray(e,s);if("function"==typeof r&&!1===(n=r(s,e))&&!qc(a)&&"string"!=typeof a&&"boolean"!=typeof a&&a instanceof Object&&(this.allowCreation=!1,n=this._handleObject(a,l,!0),this.allowCreation=!1===i),!1!==n){o=!0;var h=this._getValue(l);if(qc(a))this._handleArray(a,h,l);else if("string"==typeof a)this._makeTextInput(a,h,l);else if("boolean"==typeof a)this._makeCheckbox(a,h,l);else if(a instanceof Object){var u=!0;if(-1!==av(e).call(e,"physics")&&this.moduleOptions.physics.solver!==s&&(u=!1),!0===u)if(void 0!==a.enabled){var d=wE.copyAndExtendArray(l,"enabled"),c=this._getValue(d);if(!0===c){var p=this._makeLabel(s,l,!0);this._makeItem(l,p),o=this._handleObject(a,l)||o}else this._makeCheckbox(a,c,l)}else{var f=this._makeLabel(s,l,!0);this._makeItem(l,f),o=this._handleObject(a,l)||o}}else console.error("dont know how to handle",a,s,l)}}return o}},{key:"_handleArray",value:function(t,e,i){"string"==typeof t[0]&&"color"===t[0]?(this._makeColorField(t,e,i),t[1]!==e&&this.changedOptions.push({path:i,value:e})):"string"==typeof t[0]?(this._makeDropdown(t,e,i),t[0]!==e&&this.changedOptions.push({path:i,value:e})):"number"==typeof t[0]&&(this._makeRange(t,e,i),t[0]!==e&&this.changedOptions.push({path:i,value:Number(e)}))}},{key:"_update",value:function(t,e){var i=this._constructOptions(t,e);this.parent.body&&this.parent.body.emitter&&this.parent.body.emitter.emit&&this.parent.body.emitter.emit("configChange",i),this.initialized=!0,this.parent.setOptions(i)}},{key:"_constructOptions",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=i;t="false"!==(t="true"===t||t)&&t;for(var r=0;rvar options = "+vv(t,null,2)+""}},{key:"getOptions",value:function(){for(var t={},e=0;eo)&&(o=i)})),null!==r&&null!==o){var s=this,a=this.itemSet.items[i[0]],l=-1*this._getScrollTop(),h=null,u=function(){var t=wI(s,a);t.shouldScroll&&t.itemTop!=h.itemTop&&(s._setScrollTop(-t.scrollOffset),s._redraw())},d=!e||void 0===e.zoom||e.zoom,c=(r+o)/2,p=d?1.1*(o-r):Math.max(this.range.end-this.range.start,1.1*(o-r)),f=!e||void 0===e.animation||e.animation;f||(h={shouldScroll:!1,scrollOffset:-1,itemTop:-1}),this.range.setRange(c-p/2,c+p/2,{animation:f},(function(){u(),Rv(u,100)}),(function(t,e,i){var n=wI(s,a);if(!1!==n&&(h||(h=n),h.itemTop!=n.itemTop||h.shouldScroll)){h.itemTop!=n.itemTop&&n.shouldScroll&&(h=n,l=-1*s._getScrollTop());var r=l,o=h.scrollOffset,u=i?o:r+(o-r)*t;s._setScrollTop(-u),e||s._redraw()}}))}}}},{key:"fit",value:function(t,e){var i,n=!t||void 0===t.animation||t.animation;1===this.itemsData.length&&void 0===this.itemsData.get()[0].end?(i=this.getDataRange(),this.moveTo(i.min.valueOf(),{animation:n},e)):(i=this.getItemRange(),this.range.setRange(i.min,i.max,{animation:n},e))}},{key:"getItemRange",value:function(){var t=this,e=this.getDataRange(),i=null!==e.min?e.min.valueOf():null,n=null!==e.max?e.max.valueOf():null,r=null,o=null;if(null!=i&&null!=n){var s=n-i;s<=0&&(s=10);var a=s/this.props.center.width,l={},h=0;if(Hp(wE).call(wE,this.itemSet.items,(function(t,e){if(t.groupShowing){l[e]=t.redraw(!0),h=l[e].length}})),h>0)for(var u=function(t){Hp(wE).call(wE,l,(function(e){e[t]()}))},d=0;dn&&(n=l,o=e)})),r&&o){var c=r.getWidthLeft()+10,p=o.getWidthRight()+10,f=this.props.center.width-c-p;f>0&&(this.options.rtl?(i=bI(r)-p*s/f,n=_I(o)+c*s/f):(i=bI(r)-c*s/f,n=_I(o)+p*s/f))}}return{min:null!=i?new Date(i):null,max:null!=n?new Date(n):null}}},{key:"getDataRange",value:function(){var t,e=null,i=null;this.itemsData&&Hp(t=this.itemsData).call(t,(function(t){var n=wE.convert(t.start,"Date").valueOf(),r=wE.convert(null!=t.end?t.end:t.start,"Date").valueOf();(null===e||ni)&&(i=r)}));return{min:null!=e?new Date(e):null,max:null!=i?new Date(i):null}}},{key:"getEventProperties",value:function(t){var e=t.center?t.center.x:t.clientX,i=t.center?t.center.y:t.clientY,n=this.dom.centerContainer.getBoundingClientRect(),r=this.options.rtl?n.right-e:e-n.left,o=i-n.top,s=this.itemSet.itemFromTarget(t),a=this.itemSet.groupFromTarget(t),l=NP.customTimeFromTarget(t),h=this.itemSet.options.snap||null,u=this.body.util.getScale(),d=this.body.util.getStep(),c=this._toTime(r),p=h?h(c,u,d):c,f=wE.getTarget(t),m=null;return null!=s?m="item":null!=l?m="custom-time":wE.hasParent(f,this.timeAxis.dom.foreground)||this.timeAxis2&&wE.hasParent(f,this.timeAxis2.dom.foreground)?m="axis":wE.hasParent(f,this.itemSet.dom.labelSet)?m="group-label":wE.hasParent(f,this.currentTime.bar)?m="current-time":wE.hasParent(f,this.dom.center)&&(m="background"),{event:t,item:s?s.id:null,isCluster:!!s&&!!s.isCluster,items:s?s.items||[]:null,group:a?a.groupId:null,customTime:l?l.options.id:null,what:m,pageX:t.srcEvent?t.srcEvent.pageX:t.pageX,pageY:t.srcEvent?t.srcEvent.pageY:t.pageY,x:r,y:o,time:c,snappedTime:p}}},{key:"toggleRollingMode",value:function(){this.range.rolling?this.range.stopRolling():(null==this.options.rollingMode&&this.setOptions(this.options),this.range.startRolling())}},{key:"_redraw",value:function(){RP.prototype._redraw.call(this)}},{key:"_onFit",value:function(t){var e=t.start,i=t.end,n=t.animation;i?this.range.setRange(e,i,{animation:n}):this.moveTo(e.valueOf(),{animation:n})}}]),i}(RP);function bI(t){return wE.convert(t.data.start,"Date").valueOf()}function _I(t){var e=null!=t.data.end?t.data.end:t.data.start;return wE.convert(e,"Date").valueOf()}function wI(t,e){if(!e.parent)return!1;var i=t.options.rtl?t.props.rightContainer.height:t.props.leftContainer.height,n=t.props.center.height,r=e.parent,o=r.top,s=!0,a=t.timeAxis.options.orientation.axis,l=function(){return"bottom"==a?r.height-e.top-e.height:e.top},h=-1*t._getScrollTop(),u=o+l(),d=e.height;return uh+i?o+=l()+d-i+t.itemSet.options.margin.item.vertical:s=!1,{shouldScroll:s,scrollOffset:o=Math.min(o,n-i),itemTop:u}}var kI=function(){function t(e,i,n,r,o,s){var a=arguments.length>6&&void 0!==arguments[6]&&arguments[6],l=arguments.length>7&&void 0!==arguments[7]&&arguments[7];if(Ma(this,t),this.majorSteps=[1,2,5,10],this.minorSteps=[.25,.5,1,2],this.customLines=null,this.containerHeight=o,this.majorCharHeight=s,this._start=e,this._end=i,this.scale=1,this.minorStepIdx=-1,this.magnitudefactor=1,this.determineScale(),this.zeroAlign=a,this.autoScaleStart=n,this.autoScaleEnd=r,this.formattingFunction=l,n||r){var h=this,u=function(t){var e=t-t%(h.magnitudefactor*h.minorSteps[h.minorStepIdx]);return t%(h.magnitudefactor*h.minorSteps[h.minorStepIdx])>h.magnitudefactor*h.minorSteps[h.minorStepIdx]*.5?e+h.magnitudefactor*h.minorSteps[h.minorStepIdx]:e};n&&(this._start-=2*this.magnitudefactor*this.minorSteps[this.minorStepIdx],this._start=u(this._start)),r&&(this._end+=this.magnitudefactor*this.minorSteps[this.minorStepIdx],this._end=u(this._end)),this.determineScale()}}return Yd(t,[{key:"setCharHeight",value:function(t){this.majorCharHeight=t}},{key:"setHeight",value:function(t){this.containerHeight=t}},{key:"determineScale",value:function(){var t=this._end-this._start;this.scale=this.containerHeight/t;var e=this.majorCharHeight/this.scale,i=t>0?Math.round(Math.log(t)/Math.LN10):0;this.minorStepIdx=-1,this.magnitudefactor=Math.pow(10,i);var n=0;i<0&&(n=i);for(var r=!1,o=n;Math.abs(o)<=Math.abs(i);o++){this.magnitudefactor=Math.pow(10,o);for(var s=0;s=e){r=!0,this.minorStepIdx=s;break}}if(!0===r)break}}},{key:"is_major",value:function(t){return t%(this.magnitudefactor*this.majorSteps[this.minorStepIdx])==0}},{key:"getStep",value:function(){return this.magnitudefactor*this.minorSteps[this.minorStepIdx]}},{key:"getFirstMajor",value:function(){var t=this.magnitudefactor*this.majorSteps[this.minorStepIdx];return this.convertValue(this._start+(t-this._start%t)%t)}},{key:"formatValue",value:function(t){var e=t.toPrecision(5);return"function"==typeof this.formattingFunction&&(e=this.formattingFunction(t)),"number"==typeof e?"".concat(e):"string"==typeof e?e:t.toPrecision(5)}},{key:"getLines",value:function(){for(var t=[],e=this.getStep(),i=(e-this._start%e)%e,n=this._start+i;this._end-n>1e-5;n+=e)n!=this._start&&t.push({major:this.is_major(n),y:this.convertValue(n),val:this.formatValue(n)});return t}},{key:"followScale",value:function(t){var e=this.minorStepIdx,i=this._start,n=this._end,r=this,o=function(){r.magnitudefactor*=2},s=function(){r.magnitudefactor/=2};t.minorStepIdx<=1&&this.minorStepIdx<=1||t.minorStepIdx>1&&this.minorStepIdx>1||(t.minorStepIdxn+1e-5)s(),h=!1;else{if(!this.autoScaleStart&&this._start=0)){s(),h=!1;continue}console.warn("Can't adhere to given 'min' range, due to zeroalign")}this.autoScaleStart&&this.autoScaleEnd&&d=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(t){throw t},f:r}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,s=!0,a=!1;return{s:function(){i=i.call(t)},n:function(){var t=i.next();return s=t.done,t},e:function(t){a=!0,o=t},f:function(){try{s||null==i.return||i.return()}finally{if(a)throw o}}}}function DI(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i=0&&t._redrawLabel(n-2,e.val,i,"vis-y-axis vis-major",t.props.majorCharHeight),!0===t.master&&(r?t._redrawLine(n,i,"vis-grid vis-horizontal vis-major",t.options.majorLinesOffset,t.props.majorLineWidth):t._redrawLine(n,i,"vis-grid vis-horizontal vis-minor",t.options.minorLinesOffset,t.props.minorLineWidth))}));var a=0;void 0!==this.options[i].title&&void 0!==this.options[i].title.text&&(a=this.props.titleCharHeight);var l=!0===this.options.icons?Math.max(this.options.iconWidth,a)+this.options.labelOffsetX+15:a+this.options.labelOffsetX+15;return this.maxLabelSize>this.width-l&&!0===this.options.visible?(this.width=this.maxLabelSize+l,this.options.width="".concat(this.width,"px"),Vb(this.DOMelements.lines),Vb(this.DOMelements.labels),this.redraw(),e=!0):this.maxLabelSizethis.minWidth?(this.width=Math.max(this.minWidth,this.maxLabelSize+l),this.options.width="".concat(this.width,"px"),Vb(this.DOMelements.lines),Vb(this.DOMelements.labels),this.redraw(),e=!0):(Vb(this.DOMelements.lines),Vb(this.DOMelements.labels),e=!1),e}},{key:"convertValue",value:function(t){return this.scale.convertValue(t)}},{key:"screenToValue",value:function(t){return this.scale.screenToValue(t)}},{key:"_redrawLabel",value:function(t,e,i,n,r){var o=$b("div",this.DOMelements.labels,this.dom.frame);o.className=n,o.innerHTML=wE.xss(e),"left"===i?(o.style.left="-".concat(this.options.labelOffsetX,"px"),o.style.textAlign="right"):(o.style.right="-".concat(this.options.labelOffsetX,"px"),o.style.textAlign="left"),o.style.top="".concat(t-.5*r+this.options.labelOffsetY,"px"),e+="";var s=Math.max(this.props.majorCharWidth,this.props.minorCharWidth);this.maxLabelSize0&&(i=Math.min(i,Math.abs(e[n-1].screen_x-e[n].screen_x))),0===i&&(void 0===t[e[n].screen_x]&&(t[e[n].screen_x]={amount:0,resolved:0,accumulatedPositive:0,accumulatedNegative:0}),t[e[n].screen_x].amount+=1)},OI._getSafeDrawData=function(t,e,i){var n,r;return t0?(n=t0){_T(t).call(t,(function(t,e){return t.screen_x===e.screen_x?t.groupIde[o].screen_y?e[o].screen_y:n,r=rt[s].accumulatedNegative?t[s].accumulatedNegative:n)>t[s].accumulatedPositive?t[s].accumulatedPositive:n,r=(r=r0){return 1==e.options.interpolation.enabled?EI._catmullRom(t,e):EI._linear(t)}},EI.drawIcon=function(t,e,i,n,r,o){var s,a,l=.5*r,h=qb("rect",o.svgElements,o.svg);(h.setAttributeNS(null,"x",e),h.setAttributeNS(null,"y",i-l),h.setAttributeNS(null,"width",n),h.setAttributeNS(null,"height",2*l),h.setAttributeNS(null,"class","vis-outline"),(s=qb("path",o.svgElements,o.svg)).setAttributeNS(null,"class",t.className),void 0!==t.style&&s.setAttributeNS(null,"style",t.style),s.setAttributeNS(null,"d","M"+e+","+i+" L"+(e+n)+","+i),1==t.options.shaded.enabled&&(a=qb("path",o.svgElements,o.svg),"top"==t.options.shaded.orientation?a.setAttributeNS(null,"d","M"+e+", "+(i-l)+"L"+e+","+i+" L"+(e+n)+","+i+" L"+(e+n)+","+(i-l)):a.setAttributeNS(null,"d","M"+e+","+i+" L"+e+","+(i+l)+" L"+(e+n)+","+(i+l)+"L"+(e+n)+","+i),a.setAttributeNS(null,"class",t.className+" vis-icon-fill"),void 0!==t.options.shaded.style&&""!==t.options.shaded.style&&a.setAttributeNS(null,"style",t.options.shaded.style)),1==t.options.drawPoints.enabled)&&Zb(e+.5*n,i,{style:t.options.drawPoints.style,styles:t.options.drawPoints.styles,size:t.options.drawPoints.size,className:t.className},o.svgElements,o.svg)},EI.drawShading=function(t,e,i,n){if(1==e.options.shaded.enabled){var r,o=Number(n.svg.style.height.replace("px","")),s=qb("path",n.svgElements,n.svg),a="L";1==e.options.interpolation.enabled&&(a="C");var l=0;l="top"==e.options.shaded.orientation?0:"bottom"==e.options.shaded.orientation?o:Math.min(Math.max(0,e.zeroPosition),o),r="group"==e.options.shaded.orientation&&null!=i&&null!=i?"M"+t[0][0]+","+t[0][1]+" "+this.serializePath(t,a,!1)+" L"+i[i.length-1][0]+","+i[i.length-1][1]+" "+this.serializePath(i,a,!0)+i[0][0]+","+i[0][1]+" Z":"M"+t[0][0]+","+t[0][1]+" "+this.serializePath(t,a,!1)+" V"+l+" H"+t[0][0]+" Z",s.setAttributeNS(null,"class",e.className+" vis-fill"),void 0!==e.options.shaded.style&&s.setAttributeNS(null,"style",e.options.shaded.style),s.setAttributeNS(null,"d",r)}},EI.draw=function(t,e,i){if(null!=t&&null!=t){var n=qb("path",i.svgElements,i.svg);n.setAttributeNS(null,"class",e.className),void 0!==e.style&&n.setAttributeNS(null,"style",e.style);var r="L";1==e.options.interpolation.enabled&&(r="C"),n.setAttributeNS(null,"d","M"+t[0][0]+","+t[0][1]+" "+this.serializePath(t,r,!1))}},EI.serializePath=function(t,e,i){if(t.length<2)return"";var n,r=e;if(i)for(n=t.length-2;n>0;n--)r+=t[n][0]+","+t[n][1]+" ";else for(n=1;n0&&(f=1/f),(m=3*v*(v+g))>0&&(m=1/m),a={screen_x:(-b*n.screen_x+c*r.screen_x+_*o.screen_x)*f,screen_y:(-b*n.screen_y+c*r.screen_y+_*o.screen_y)*f},l={screen_x:(y*r.screen_x+p*o.screen_x-b*s.screen_x)*m,screen_y:(y*r.screen_y+p*o.screen_y-b*s.screen_y)*m},0==a.screen_x&&0==a.screen_y&&(a=r),0==l.screen_x&&0==l.screen_y&&(l=o),k.push([a.screen_x,a.screen_y]),k.push([l.screen_x,l.screen_y]),k.push([o.screen_x,o.screen_y]);return k},EI._linear=function(t){for(var e=[],i=0;ie.x?1:-1}))):this.itemsData=[]},PI.prototype.getItems=function(){return this.itemsData},PI.prototype.setZeroPosition=function(t){this.zeroPosition=t},PI.prototype.setOptions=function(t){if(void 0!==t){wE.selectiveDeepExtend(["sampling","style","sort","yAxisOrientation","barChart","zIndex","excludeFromStacking","excludeFromLegend"],this.options,t),"function"==typeof t.drawPoints&&(t.drawPoints={onRender:t.drawPoints}),wE.mergeOptions(this.options,t,"interpolation"),wE.mergeOptions(this.options,t,"drawPoints"),wE.mergeOptions(this.options,t,"shaded"),t.interpolation&&"object"==Nd(t.interpolation)&&t.interpolation.parametrization&&("uniform"==t.interpolation.parametrization?this.options.interpolation.alpha=0:"chordal"==t.interpolation.parametrization?this.options.interpolation.alpha=1:(this.options.interpolation.parametrization="centripetal",this.options.interpolation.alpha=.5))}},PI.prototype.update=function(t){this.group=t,this.content=t.content||"graph",this.className=t.className||this.className||"vis-graph-group"+this.groupsUsingDefaultStyles[0]%10,this.visible=void 0===t.visible||t.visible,this.style=t.style,this.setOptions(t.options)},PI.prototype.getLegend=function(t,e,i,n,r){null!=i&&null!=i||(i={svg:document.createElementNS("http://www.w3.org/2000/svg","svg"),svgElements:{},options:this.options,groups:[this]});switch(null!=n&&null!=n||(n=0),null!=r&&null!=r||(r=.5*e),this.options.style){case"line":EI.drawIcon(this,n,r,t,e,i);break;case"points":case"point":TI.drawIcon(this,n,r,t,e,i);break;case"bar":OI.drawIcon(this,n,r,t,e,i)}return{icon:i.svg,label:this.content,orientation:this.options.yAxisOrientation}},PI.prototype.getYRange=function(t){for(var e=t[0].y,i=t[0].y,n=0;nt[n].y?t[n].y:e,i=i");this.dom.textArea.innerHTML=wE.xss(o),this.dom.textArea.style.lineHeight=.75*this.options.iconSize+this.options.iconSpacing+"px"}},AI.prototype.drawLegendIcons=function(){if(this.dom.frame.parentNode){var t=rp(this.groups);_T(t).call(t,(function(t,e){return t0){var s={};for(this._getRelevantData(o,s,n,r),this._applySampling(o,s),e=0;e0)switch(t.options.style){case"line":l.hasOwnProperty(o[e])||(l[o[e]]=EI.calcPath(s[o[e]],t)),EI.draw(l[o[e]],t,this.framework);case"point":case"points":"point"!=t.options.style&&"points"!=t.options.style&&1!=t.options.drawPoints.enabled||TI.draw(s[o[e]],t,this.framework)}}}return Vb(this.svgElements),!1},LI.prototype._stack=function(t,e){var i,n,r,o,s;i=0;for(var a=0;at[a].x){s=e[l],o=0==l?s:e[l-1],i=l;break}}void 0===s&&(o=e[e.length-1],s=e[e.length-1]),n=s.x-o.x,r=s.y-o.y,t[a].y=0==n?t[a].orginalY+s.y:t[a].orginalY+r/n*(t[a].x-o.x)+o.y}},LI.prototype._getRelevantData=function(t,e,i,n){var r,o,s,a;if(t.length>0)for(o=0;o0)for(var i=0;i0){var r,o=n.length,s=o/(this.body.util.toGlobalScreen(n[n.length-1].x)-this.body.util.toGlobalScreen(n[0].x));r=Math.min(Math.ceil(.2*o),Math.max(1,Math.round(s)));for(var a=new Array(o),l=0;l0){for(o=0;o0&&(r=this.groups[t[o]],!0===s.stack&&"bar"===s.style?"left"===s.yAxisOrientation?a=Yc(a).call(a,n):l=Yc(l).call(l,n):i[t[o]]=r.getYRange(n,t[o]));OI.getStackedYRange(a,i,t,"__barStackLeft","left"),OI.getStackedYRange(l,i,t,"__barStackRight","right")}},LI.prototype._updateYAxis=function(t,e){var i,n,r=!1,o=!1,s=!1,a=1e9,l=1e9,h=-1e9,u=-1e9;if(t.length>0){for(var d=0;di?i:a,h=hi?i:l,u=uo?o:t,e=null==e||e0&&h.push(u.screenToValue(r)),!d.hidden&&this.itemsData.length>0&&h.push(d.screenToValue(r)),{event:t,customTime:s?s.options.id:null,what:l,pageX:t.srcEvent?t.srcEvent.pageX:t.pageX,pageY:t.srcEvent?t.srcEvent.pageY:t.pageY,x:n,y:r,time:o,value:h}},GI.prototype._createConfigurator=function(){return new vI(this,this.dom.container,BI)};var WI=Jb();sO.locale(WI);var UI={Core:RP,DateUtil:nP,Range:oP,stack:gA,TimeStep:cP,components:{items:{Item:DA,BackgroundItem:AA,BoxItem:CA,ClusterItem:WA,PointItem:MA,RangeItem:EA},BackgroundGroup:wA,Component:IE,CurrentTime:jP,CustomTime:NP,DataAxis:CI,DataScale:kI,GraphGroup:PI,Group:bA,ItemSet:tI,Legend:AI,LineGraph:LI,TimeAxis:mP}};t.DOMutil=Qb,t.DataSet=nO,t.DataView=rO,t.Graph2d=GI,t.Hammer=uP,t.Queue=tO,t.Timeline=yI,t.keycharm=gP,t.moment=sO,t.timeline=UI,t.util=Wb}));
+//# sourceMappingURL=vis-timeline-graph2d.min.js.map
diff --git a/web/skins/classic/assets/vis-timeline/styles/vis-timeline-graph2d.min.css b/web/skins/classic/assets/vis-timeline/styles/vis-timeline-graph2d.min.css
new file mode 100644
index 0000000000..837f259661
--- /dev/null
+++ b/web/skins/classic/assets/vis-timeline/styles/vis-timeline-graph2d.min.css
@@ -0,0 +1,2 @@
+.vis-time-axis{overflow:hidden;position:relative}.vis-time-axis.vis-foreground{left:0;top:0;width:100%}.vis-time-axis.vis-background{height:100%;left:0;position:absolute;top:0;width:100%}.vis-time-axis .vis-text{box-sizing:border-box;color:#4d4d4d;overflow:hidden;padding:3px;position:absolute;white-space:nowrap}.vis-time-axis .vis-text.vis-measure{margin-left:0;margin-right:0;padding-left:0;padding-right:0;position:absolute;visibility:hidden}.vis-time-axis .vis-grid.vis-vertical{border-left:1px solid;position:absolute}.vis-time-axis .vis-grid.vis-vertical-rtl{border-right:1px solid;position:absolute}.vis-time-axis .vis-grid.vis-minor{border-color:#e5e5e5}.vis-time-axis .vis-grid.vis-major{border-color:#bfbfbf}.vis .overlay{height:100%;left:0;position:absolute;top:0;width:100%;z-index:10}.vis-active{box-shadow:0 0 10px #86d5f8}.vis-custom-time{background-color:#6e94ff;cursor:move;width:2px;z-index:1}.vis-custom-time>.vis-custom-time-marker{background-color:inherit;color:#fff;cursor:auto;font-size:12px;padding:3px 5px;top:0;white-space:nowrap;z-index:inherit}.vis-current-time{background-color:#ff7f6e;pointer-events:none;width:2px;z-index:1}.vis-rolling-mode-btn{background:#3876c2;border-radius:50%;color:#fff;cursor:pointer;font-size:28px;font-weight:700;height:40px;opacity:.8;position:absolute;right:20px;text-align:center;top:7px;width:40px}.vis-rolling-mode-btn:before{content:"\26F6"}.vis-rolling-mode-btn:hover{opacity:1}.vis-panel{box-sizing:border-box;margin:0;padding:0;position:absolute}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right,.vis-panel.vis-top{border:1px #bfbfbf}.vis-panel.vis-center,.vis-panel.vis-left,.vis-panel.vis-right{border-bottom-style:solid;border-top-style:solid;overflow:hidden}.vis-left.vis-panel.vis-vertical-scroll,.vis-right.vis-panel.vis-vertical-scroll{height:100%;overflow-x:hidden;overflow-y:scroll}.vis-left.vis-panel.vis-vertical-scroll{direction:rtl}.vis-left.vis-panel.vis-vertical-scroll .vis-content,.vis-right.vis-panel.vis-vertical-scroll{direction:ltr}.vis-right.vis-panel.vis-vertical-scroll .vis-content{direction:rtl}.vis-panel.vis-bottom,.vis-panel.vis-center,.vis-panel.vis-top{border-left-style:solid;border-right-style:solid}.vis-background{overflow:hidden}.vis-panel>.vis-content{position:relative}.vis-panel .vis-shadow{box-shadow:0 0 10px rgba(0,0,0,.8);height:1px;position:absolute;width:100%}.vis-panel .vis-shadow.vis-top{left:0;top:-1px}.vis-panel .vis-shadow.vis-bottom{bottom:-1px;left:0}.vis-graph-group0{fill:#4f81bd;fill-opacity:0;stroke-width:2px;stroke:#4f81bd}.vis-graph-group1{fill:#f79646;fill-opacity:0;stroke-width:2px;stroke:#f79646}.vis-graph-group2{fill:#8c51cf;fill-opacity:0;stroke-width:2px;stroke:#8c51cf}.vis-graph-group3{fill:#75c841;fill-opacity:0;stroke-width:2px;stroke:#75c841}.vis-graph-group4{fill:#ff0100;fill-opacity:0;stroke-width:2px;stroke:#ff0100}.vis-graph-group5{fill:#37d8e6;fill-opacity:0;stroke-width:2px;stroke:#37d8e6}.vis-graph-group6{fill:#042662;fill-opacity:0;stroke-width:2px;stroke:#042662}.vis-graph-group7{fill:#00ff26;fill-opacity:0;stroke-width:2px;stroke:#00ff26}.vis-graph-group8{fill:#f0f;fill-opacity:0;stroke-width:2px;stroke:#f0f}.vis-graph-group9{fill:#8f3938;fill-opacity:0;stroke-width:2px;stroke:#8f3938}.vis-timeline .vis-fill{fill-opacity:.1;stroke:none}.vis-timeline .vis-bar{fill-opacity:.5;stroke-width:1px}.vis-timeline .vis-point{stroke-width:2px;fill-opacity:1}.vis-timeline .vis-legend-background{stroke-width:1px;fill-opacity:.9;fill:#fff;stroke:#c2c2c2}.vis-timeline .vis-outline{stroke-width:1px;fill-opacity:1;fill:#fff;stroke:#e5e5e5}.vis-timeline .vis-icon-fill{fill-opacity:.3;stroke:none}.vis-timeline{border:1px solid #bfbfbf;box-sizing:border-box;margin:0;overflow:hidden;padding:0;position:relative}.vis-loading-screen{height:100%;left:0;position:absolute;top:0;width:100%}.vis [class*=span]{min-height:0;width:auto}.vis-item{background-color:#d5ddf6;border-color:#97b0f8;border-width:1px;color:#1a1a1a;display:inline-block;position:absolute;z-index:1}.vis-item.vis-selected{background-color:#fff785;border-color:#ffc200;z-index:2}.vis-editable.vis-selected{cursor:move}.vis-item.vis-point.vis-selected{background-color:#fff785}.vis-item.vis-box{border-radius:2px;border-style:solid;text-align:center}.vis-item.vis-point{background:none}.vis-item.vis-dot{border-radius:4px;border-style:solid;border-width:4px;padding:0;position:absolute}.vis-item.vis-range{border-radius:2px;border-style:solid;box-sizing:border-box}.vis-item.vis-background{background-color:rgba(213,221,246,.4);border:none;box-sizing:border-box;margin:0;padding:0}.vis-item .vis-item-overflow{height:100%;margin:0;overflow:hidden;padding:0;position:relative;width:100%}.vis-item-visible-frame{white-space:nowrap}.vis-item.vis-range .vis-item-content{display:inline-block;position:relative}.vis-item.vis-background .vis-item-content{display:inline-block;position:absolute}.vis-item.vis-line{border-left-style:solid;border-left-width:1px;padding:0;position:absolute;width:0}.vis-item .vis-item-content{box-sizing:border-box;padding:5px;white-space:nowrap}.vis-item .vis-onUpdateTime-tooltip{background:#4f81bd;border-radius:1px;color:#fff;padding:5px;position:absolute;text-align:center;transition:.4s;-o-transition:.4s;-moz-transition:.4s;-webkit-transition:.4s;white-space:nowrap;width:200px}.vis-item .vis-delete,.vis-item .vis-delete-rtl{box-sizing:border-box;cursor:pointer;height:24px;padding:0 5px;position:absolute;top:0;-webkit-transition:background .2s linear;-moz-transition:background .2s linear;-ms-transition:background .2s linear;-o-transition:background .2s linear;transition:background .2s linear;width:24px}.vis-item .vis-delete{right:-24px}.vis-item .vis-delete-rtl{left:-24px}.vis-item .vis-delete-rtl:after,.vis-item .vis-delete:after{color:red;content:"\00D7";font-family:arial,sans-serif;font-size:22px;font-weight:700;-webkit-transition:color .2s linear;-moz-transition:color .2s linear;-ms-transition:color .2s linear;-o-transition:color .2s linear;transition:color .2s linear}.vis-item .vis-delete-rtl:hover,.vis-item .vis-delete:hover{background:red}.vis-item .vis-delete-rtl:hover:after,.vis-item .vis-delete:hover:after{color:#fff}.vis-item .vis-drag-center{cursor:move;height:100%;left:0;position:absolute;top:0;width:100%}.vis-item.vis-range .vis-drag-left{cursor:w-resize;left:-4px}.vis-item.vis-range .vis-drag-left,.vis-item.vis-range .vis-drag-right{height:100%;max-width:20%;min-width:2px;position:absolute;top:0;width:24px}.vis-item.vis-range .vis-drag-right{cursor:e-resize;right:-4px}.vis-range.vis-item.vis-readonly .vis-drag-left,.vis-range.vis-item.vis-readonly .vis-drag-right{cursor:auto}.vis-item.vis-cluster{border-radius:2px;border-style:solid;text-align:center;vertical-align:center}.vis-item.vis-cluster-line{border-left-style:solid;border-left-width:1px;padding:0;position:absolute;width:0}.vis-item.vis-cluster-dot{border-radius:4px;border-style:solid;border-width:4px;padding:0;position:absolute}div.vis-tooltip{background-color:#f5f4ed;border:1px solid #808074;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;box-shadow:3px 3px 10px rgba(0,0,0,.2);color:#000;font-family:verdana;font-size:14px;padding:5px;pointer-events:none;position:absolute;visibility:hidden;white-space:nowrap;z-index:5}.vis-itemset{box-sizing:border-box;margin:0;padding:0;position:relative}.vis-itemset .vis-background,.vis-itemset .vis-foreground{height:100%;overflow:visible;position:absolute;width:100%}.vis-axis{height:0;left:0;position:absolute;width:100%;z-index:1}.vis-foreground .vis-group{border-bottom:1px solid #bfbfbf;box-sizing:border-box;position:relative}.vis-foreground .vis-group:last-child{border-bottom:none}.vis-nesting-group{cursor:pointer}.vis-label.vis-nested-group.vis-group-level-unknown-but-gte1{background:#f5f5f5}.vis-label.vis-nested-group.vis-group-level-0{background-color:#fff}.vis-ltr .vis-label.vis-nested-group.vis-group-level-0 .vis-inner{padding-left:0}.vis-rtl .vis-label.vis-nested-group.vis-group-level-0 .vis-inner{padding-right:0}.vis-label.vis-nested-group.vis-group-level-1{background-color:rgba(0,0,0,.05)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-1 .vis-inner{padding-left:15px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-1 .vis-inner{padding-right:15px}.vis-label.vis-nested-group.vis-group-level-2{background-color:rgba(0,0,0,.1)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-2 .vis-inner{padding-left:30px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-2 .vis-inner{padding-right:30px}.vis-label.vis-nested-group.vis-group-level-3{background-color:rgba(0,0,0,.15)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-3 .vis-inner{padding-left:45px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-3 .vis-inner{padding-right:45px}.vis-label.vis-nested-group.vis-group-level-4{background-color:rgba(0,0,0,.2)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-4 .vis-inner{padding-left:60px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-4 .vis-inner{padding-right:60px}.vis-label.vis-nested-group.vis-group-level-5{background-color:rgba(0,0,0,.25)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-5 .vis-inner{padding-left:75px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-5 .vis-inner{padding-right:75px}.vis-label.vis-nested-group.vis-group-level-6{background-color:rgba(0,0,0,.3)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-6 .vis-inner{padding-left:90px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-6 .vis-inner{padding-right:90px}.vis-label.vis-nested-group.vis-group-level-7{background-color:rgba(0,0,0,.35)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-7 .vis-inner{padding-left:105px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-7 .vis-inner{padding-right:105px}.vis-label.vis-nested-group.vis-group-level-8{background-color:rgba(0,0,0,.4)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-8 .vis-inner{padding-left:120px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-8 .vis-inner{padding-right:120px}.vis-label.vis-nested-group.vis-group-level-9{background-color:rgba(0,0,0,.45)}.vis-ltr .vis-label.vis-nested-group.vis-group-level-9 .vis-inner{padding-left:135px}.vis-rtl .vis-label.vis-nested-group.vis-group-level-9 .vis-inner{padding-right:135px}.vis-label.vis-nested-group{background-color:rgba(0,0,0,.5)}.vis-ltr .vis-label.vis-nested-group .vis-inner{padding-left:150px}.vis-rtl .vis-label.vis-nested-group .vis-inner{padding-right:150px}.vis-group-level-unknown-but-gte1{border:1px solid red}.vis-label.vis-nesting-group:before{display:inline-block;width:15px}.vis-label.vis-nesting-group.expanded:before{content:"\25BC"}.vis-label.vis-nesting-group.collapsed:before{content:"\25B6"}.vis-rtl .vis-label.vis-nesting-group.collapsed:before{content:"\25C0"}.vis-ltr .vis-label:not(.vis-nesting-group):not(.vis-group-level-0){padding-left:15px}.vis-rtl .vis-label:not(.vis-nesting-group):not(.vis-group-level-0){padding-right:15px}.vis-overlay{height:100%;left:0;position:absolute;top:0;width:100%;z-index:10}.vis-labelset{overflow:hidden}.vis-labelset,.vis-labelset .vis-label{box-sizing:border-box;position:relative}.vis-labelset .vis-label{border-bottom:1px solid #bfbfbf;color:#4d4d4d;left:0;top:0;width:100%}.vis-labelset .vis-label.draggable{cursor:pointer}.vis-group-is-dragging{background:rgba(0,0,0,.1)}.vis-labelset .vis-label:last-child{border-bottom:none}.vis-labelset .vis-label .vis-inner{display:inline-block;padding:5px}.vis-labelset .vis-label .vis-inner.vis-hidden{padding:0}div.vis-configuration{display:block;float:left;font-size:12px;position:relative}div.vis-configuration-wrapper{display:block;width:700px}div.vis-configuration-wrapper:after{clear:both;content:"";display:block}div.vis-configuration.vis-config-option-container{background-color:#fff;border:2px solid #f7f8fa;border-radius:4px;display:block;left:10px;margin-top:20px;padding-left:5px;width:495px}div.vis-configuration.vis-config-button{background-color:#f7f8fa;border:2px solid #ceced0;border-radius:4px;cursor:pointer;display:block;height:25px;left:10px;line-height:25px;margin-bottom:30px;margin-top:20px;padding-left:5px;vertical-align:middle;width:495px}div.vis-configuration.vis-config-button.hover{background-color:#4588e6;border:2px solid #214373;color:#fff}div.vis-configuration.vis-config-item{display:block;float:left;height:25px;line-height:25px;vertical-align:middle;width:495px}div.vis-configuration.vis-config-item.vis-config-s2{background-color:#f7f8fa;border-radius:3px;left:10px;padding-left:5px}div.vis-configuration.vis-config-item.vis-config-s3{background-color:#e4e9f0;border-radius:3px;left:20px;padding-left:5px}div.vis-configuration.vis-config-item.vis-config-s4{background-color:#cfd8e6;border-radius:3px;left:30px;padding-left:5px}div.vis-configuration.vis-config-header{font-size:18px;font-weight:700}div.vis-configuration.vis-config-label{height:25px;line-height:25px;width:120px}div.vis-configuration.vis-config-label.vis-config-s3{width:110px}div.vis-configuration.vis-config-label.vis-config-s4{width:100px}div.vis-configuration.vis-config-colorBlock{border:1px solid #444;border-radius:2px;cursor:pointer;height:19px;margin:0;padding:0;top:1px;width:30px}input.vis-configuration.vis-config-checkbox{left:-5px}input.vis-configuration.vis-config-rangeinput{margin:0;padding:1px;pointer-events:none;position:relative;top:-5px;width:60px}input.vis-configuration.vis-config-range{-webkit-appearance:none;background-color:transparent;border:0 solid #fff;height:20px;width:300px}input.vis-configuration.vis-config-range::-webkit-slider-runnable-track{background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(180deg,#dedede 0,#c8c8c8 99%);border:1px solid #999;border-radius:3px;box-shadow:0 0 3px 0 #aaa;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);height:5px;width:300px}input.vis-configuration.vis-config-range::-webkit-slider-thumb{-webkit-appearance:none;background:#3876c2;background:-moz-linear-gradient(top,#3876c2 0,#385380 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#3876c2),color-stop(100%,#385380));background:-webkit-linear-gradient(top,#3876c2,#385380);background:-o-linear-gradient(top,#3876c2 0,#385380 100%);background:-ms-linear-gradient(top,#3876c2 0,#385380 100%);background:linear-gradient(180deg,#3876c2 0,#385380);border:1px solid #14334b;border-radius:50%;box-shadow:0 0 1px 0 #111927;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#3876c2",endColorstr="#385380",GradientType=0);height:17px;margin-top:-7px;width:17px}input.vis-configuration.vis-config-range:focus{outline:none}input.vis-configuration.vis-config-range:focus::-webkit-slider-runnable-track{background:#9d9d9d;background:-moz-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#9d9d9d),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#9d9d9d,#c8c8c8 99%);background:-o-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#9d9d9d 0,#c8c8c8 99%);background:linear-gradient(180deg,#9d9d9d 0,#c8c8c8 99%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#9d9d9d",endColorstr="#c8c8c8",GradientType=0)}input.vis-configuration.vis-config-range::-moz-range-track{background:#dedede;background:-moz-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#dedede),color-stop(99%,#c8c8c8));background:-webkit-linear-gradient(top,#dedede,#c8c8c8 99%);background:-o-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:-ms-linear-gradient(top,#dedede 0,#c8c8c8 99%);background:linear-gradient(180deg,#dedede 0,#c8c8c8 99%);border:1px solid #999;border-radius:3px;box-shadow:0 0 3px 0 #aaa;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#dedede",endColorstr="#c8c8c8",GradientType=0);height:10px;width:300px}input.vis-configuration.vis-config-range::-moz-range-thumb{background:#385380;border:none;border-radius:50%;height:16px;width:16px}input.vis-configuration.vis-config-range:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input.vis-configuration.vis-config-range::-ms-track{background:transparent;border-color:transparent;border-width:6px 0;color:transparent;height:5px;width:300px}input.vis-configuration.vis-config-range::-ms-fill-lower{background:#777;border-radius:10px}input.vis-configuration.vis-config-range::-ms-fill-upper{background:#ddd;border-radius:10px}input.vis-configuration.vis-config-range::-ms-thumb{background:#385380;border:none;border-radius:50%;height:16px;width:16px}input.vis-configuration.vis-config-range:focus::-ms-fill-lower{background:#888}input.vis-configuration.vis-config-range:focus::-ms-fill-upper{background:#ccc}.vis-configuration-popup{background:rgba(57,76,89,.85);border:2px solid #f2faff;border-radius:4px;color:#fff;font-size:14px;height:30px;line-height:30px;position:absolute;text-align:center;-webkit-transition:opacity .3s ease-in-out;-moz-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out;width:150px}.vis-configuration-popup:after,.vis-configuration-popup:before{border:solid transparent;content:" ";height:0;left:100%;pointer-events:none;position:absolute;top:50%;width:0}.vis-configuration-popup:after{border-color:rgba(136,183,213,0) rgba(136,183,213,0) rgba(136,183,213,0) rgba(57,76,89,.85);border-width:8px;margin-top:-8px}.vis-configuration-popup:before{border-color:rgba(194,225,245,0) rgba(194,225,245,0) rgba(194,225,245,0) #f2faff;border-width:12px;margin-top:-12px}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-horizontal{border-bottom:1px solid;height:0;position:absolute;width:100%}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-minor{border-color:#e5e5e5}.vis-panel.vis-background.vis-horizontal .vis-grid.vis-major{border-color:#bfbfbf}.vis-data-axis .vis-y-axis.vis-major{color:#4d4d4d;position:absolute;white-space:nowrap;width:100%}.vis-data-axis .vis-y-axis.vis-major.vis-measure{border:0;margin:0;padding:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-minor{color:#bebebe;position:absolute;white-space:nowrap;width:100%}.vis-data-axis .vis-y-axis.vis-minor.vis-measure{border:0;margin:0;padding:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title{bottom:20px;color:#4d4d4d;position:absolute;text-align:center;white-space:nowrap}.vis-data-axis .vis-y-axis.vis-title.vis-measure{margin:0;padding:0;visibility:hidden;width:auto}.vis-data-axis .vis-y-axis.vis-title.vis-left{bottom:0;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg);-webkit-transform-origin:left top;-moz-transform-origin:left top;-ms-transform-origin:left top;-o-transform-origin:left top;transform-origin:left bottom}.vis-data-axis .vis-y-axis.vis-title.vis-right{bottom:0;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-ms-transform-origin:right bottom;-o-transform-origin:right bottom;transform-origin:right bottom}.vis-legend{background-color:rgba(247,252,255,.65);border:1px solid #b3b3b3;box-shadow:2px 2px 10px hsla(0,0%,60%,.55);padding:5px}.vis-legend-text{display:inline-block;white-space:nowrap}
+/*# sourceMappingURL=vis-timeline-graph2d.min.css.map */
diff --git a/web/skins/classic/css/base/skin.css b/web/skins/classic/css/base/skin.css
index 20a4b8263c..ddaf2b41b6 100644
--- a/web/skins/classic/css/base/skin.css
+++ b/web/skins/classic/css/base/skin.css
@@ -780,7 +780,7 @@ ul.nav.nav-pills.flex-column {
}
#mfbpanel {
- width: calc(100% - 25px);
+ width: calc(100% - 55px);
float: left;
}
@@ -1140,8 +1140,8 @@ html::-webkit-scrollbar-thumb, div::-webkit-scrollbar-thumb, nav::-webkit-scroll
/* +++ Control button block in the Stream image*/
.block-button-center {
position: absolute;
- left: 35%;
- right: 35%;
+ left: 50px;
+ right: 50px;
top: 2px;
z-index: 10;
}
@@ -1175,6 +1175,7 @@ button.btn.btn-zoom-in {
}
button.btn.btn-view-watch,
+button.btn.btn-view-event,
button.btn.btn-edit-monitor,
button.btn.btn-fullscreen {
padding: 0;
@@ -1190,6 +1191,8 @@ button.btn.btn-fullscreen {
button.btn.btn-zoom-out:focus,
button.btn.btn-zoom-in:focus,
button.btn.btn-view-watch:focus,
+button.btn.btn-view-event:focus,
+button.btn.btn-fullscreen:focus,
button.btn.btn-edit-monitor:focus {
outline: 0;
box-shadow: none;
@@ -1197,8 +1200,10 @@ button.btn.btn-edit-monitor:focus {
button.btn.btn-zoom-out:hover,
button.btn.btn-zoom-in:hover,
-button.btn.btn-edit-monitor:hover,
-button.btn.btn-view-watch:hover {
+button.btn.btn-view-watch:hover,
+button.btn.btn-view-event:hover,
+button.btn.btn-fullscreen:hover,
+button.btn.btn-edit-monitor:hover {
background-color: darkgrey;
}
diff --git a/web/skins/classic/css/base/views/montage.css b/web/skins/classic/css/base/views/montage.css
index 83094b23d9..38bbd031c9 100644
--- a/web/skins/classic/css/base/views/montage.css
+++ b/web/skins/classic/css/base/views/montage.css
@@ -1,3 +1,7 @@
+h4.alert-heading {
+ font-size: 1.5rem;
+}
+
#layout {
margin-right: 10px;
}
@@ -96,6 +100,124 @@
min-width: 130px;
}
+/* +++ Timeline */
+#timelinediv {
+ text-align: justify;
+ margin-bottom: 1rem;
+ margin-left: 1rem;
+ margin-right: 1rem;
+}
+
+#timeline-current-time {
+ position: relative;
+ height: 1.3rem;
+ min-width: 200px;
+ /*line-height: 2rem;*/
+ font-weight: bold;
+ margin-bottom: 1rem;
+ padding: 3px;
+}
+
+#timeline-extra-info {
+ position: absolute;
+ bottom: 0;
+ height: 1.3rem;
+ min-width: 200px;
+ /*line-height: 2rem;*/
+ font-weight: bold;
+ margin: 3px;
+}
+
+.vis-item.vis-range.event_timeline {
+ background-color: var(--teal);
+ /*border-color: var(--teal);*/
+ border-color: var(--blue);
+}
+
+.vis-item.vis-range.bad_event_timeline {
+ background-color: #da0c0c;
+ /*border-color: #da0c0c;*/
+ border-color: var(--blue);
+ color: antiquewhite;
+}
+
+.vis-item.vis-range.vis-selected,
+.vis-item.vis-range.event_timeline.event-archived.vis-selected {
+ background-color: #fff785;
+ border-color: #ffc200;
+ color: #1a1a1a;
+ z-index: 2;
+}
+
+.vis-item.vis-range.event_timeline.event-archived,
+.vis-item.vis-range.bad_event_timeline.event-archived {
+ background-color: var(--yellow);
+ border-color: var(--danger);
+}
+
+/* +++ Making it more compact */
+#timelinediv .vis-item .vis-item-content {
+ padding: 2px;
+}
+
+#timelinediv .vis-item {
+ top: 2px !important;
+}
+/*vis-item vis-range event_timeline vis-readonly*/
+/* --- Making it more compact */
+
+.vis-label.monitor-group-timeline {
+ min-width: 170px; /* Required to place additional information in the free space of the Timeline */
+ max-width: 40vw; /* Required for narrow screens */
+}
+
+#alert-load-events {
+ position: absolute;
+ width: 100%;
+ /*opacity: 0;*/
+ display: none;
+}
+/* --- Timeline */
+
+#dvrControls {
+ white-space: nowrap;
+ float: inline-end;
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+#buttonBlock {
+ position: relative;
+ padding-left: 1rem;
+}
+
+#buttonBlock button{
+ margin-bottom: 1rem;
+ vertical-align: top;
+}
+
+#moveLeftTimeline, #moveRightTimeline, #timeline-current-time, #downloadVideo, #last_24H, #last_8H, #last_1H, #downloadVideo {
+ float: left;
+}
+
+#speedDiv {
+ /*float: left;*/
+ float: none;
+ position: relative;
+ display: inline-block;
+ /*width: 260px;*/
+}
+
+#speedDiv #speedslider {
+ top: 10px;
+ position: relative;
+}
+
+#speedDiv #speedslideroutput {
+ width: 60px;
+ display: inline-block;
+}
+
.grid-stack-item-content.modeEditingMonitor {
cursor: crosshair;
}
diff --git a/web/skins/classic/graphics/no-frame.jpg b/web/skins/classic/graphics/no-frame.jpg
new file mode 100644
index 0000000000..c1d32b56d5
Binary files /dev/null and b/web/skins/classic/graphics/no-frame.jpg differ
diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php
index 4eda931ba3..fc1f1f2cde 100644
--- a/web/skins/classic/includes/functions.php
+++ b/web/skins/classic/includes/functions.php
@@ -72,6 +72,7 @@ function xhtmlHeadersStart($file, $title) {
if ( $basename == 'montage' ) {
echo output_link_if_exists(array('/assets/gridstack/dist/gridstack.css', '/assets/gridstack/dist/gridstack-extra.css'));
+ echo output_link_if_exists(array('/assets/vis-timeline/styles/vis-timeline-graph2d.min.css'));
}
?>
@@ -1003,6 +1004,7 @@ function xhtmlFooter() {
echo output_script_if_exists(array('assets/gridstack/dist/gridstack-all.js'));
echo output_script_if_exists(array('assets/jquery.panzoom/dist/jquery.panzoom.js'));
echo output_script_if_exists(array('js/panzoom.js'));
+ echo output_script_if_exists(array('assets/vis-timeline/standalone/umd/vis-timeline-graph2d.min.js'));
} else if ( $basename == 'watch' || $basename == 'event') {
echo output_script_if_exists(array('assets/jquery.panzoom/dist/jquery.panzoom.js'));
echo output_script_if_exists(array('js/panzoom.js'));
diff --git a/web/skins/classic/js/skin.js b/web/skins/classic/js/skin.js
index 725d56586d..4b3b89d595 100644
--- a/web/skins/classic/js/skin.js
+++ b/web/skins/classic/js/skin.js
@@ -514,7 +514,12 @@ function checkStreamForErrors(funcName, streamObj) {
Error(funcName+': stream object was null');
return true;
}
- if ( streamObj.result == "Error" ) {
+ if ( streamObj.responseJSON ) {
+ if (streamObj.responseJSON.result == "Error") {
+ Error(funcName+' stream error: '+streamObj.responseJSON.message);
+ return true;
+ }
+ } else if ( streamObj.result == "Error" ) {
Error(funcName+' stream error: '+streamObj.message);
return true;
}
@@ -1218,10 +1223,14 @@ function thisClickOnStreamObject(clickObj) {
/* For mobile device Not implemented yet. */
function thisClickOnTimeline(clickObj) {
+ console.log("thisClickOnTimeline_clickObj=====++>", clickObj);
return false;
}
var doubleTouchExecute = function(event, touchEvent) {
+console.log("touchEvent=====++>", touchEvent);
+console.log("event=====++>", event);
+console.log("this=====++>", this);
// if (touchEvent.target.id &&
// (touchEvent.target.id.indexOf('evtStream') != -1 || touchEvent.target.id.indexOf('liveStream') != -1 || touchEvent.target.id.indexOf('monitorStatus') != -1)) {
if (thisClickOnStreamObject(touchEvent.target)) {
@@ -1231,10 +1240,23 @@ var doubleTouchExecute = function(event, touchEvent) {
}
};
+/* For mobile device Not implemented yet. */
+var doubleTouchOnTimeline = function(event, touchEvent) {
+ console.log("+++doubleTouchOnTimeline_event==>", event);
+ console.log("+++doubleTouchOnTimeline_touchEvent==>", touchEvent);
+};
+
var doubleClickOnStream = function(event, touchEvent) {
+ //console.log("+++shifted==>", shifted);
+ //console.log("+++ctrled==>", ctrled);
+ //console.log("+++alted==>", alted);
if (shifted || ctrled || alted) return;
let target = null;
+//console.log("touchEvent=====++>", touchEvent);
+//console.log("event=====++>", event);
+//console.log("this=====++>", this);
if (event.target) {// Click NOT on touch screen, use THIS
+////console.log("event.target.id=====++>", event.target.id);
//Process only double clicks directly on the image, excluding clicks,
//for example, on zoom buttons and other elements located in the image area.
const fullScreenObject = thisClickOnStreamObject(event.target);
@@ -1249,6 +1271,7 @@ var doubleClickOnStream = function(event, touchEvent) {
target = event;
//}
}
+//console.log("target=====++>", target);
if (target) {
if (document.fullscreenElement) {
diff --git a/web/skins/classic/skin.php b/web/skins/classic/skin.php
index e95378b713..dbfb9bf7f8 100644
--- a/web/skins/classic/skin.php
+++ b/web/skins/classic/skin.php
@@ -23,16 +23,19 @@
if ( empty($_COOKIE['zmBandwidth']) )
$_COOKIE['zmBandwidth'] = 'low';
+if ( empty($view) ) {
+ $view = isset($user)?'console':'login';
+} else {
+ foreach ( getSkinIncludes('views/class/' . $view . '_class.php') as $includeFile )
+ require_once $includeFile;
+}
+
foreach ( getSkinIncludes('includes/config.php') as $includeFile )
require_once $includeFile;
foreach ( getSkinIncludes('includes/functions.php') as $includeFile )
require_once $includeFile;
-if ( empty($view) ) {
- $view = isset($user)?'console':'login';
-}
-
if ( isset($user) ) {
// Bandwidth Limiter
if ($user->MaxBandwidth()) {
diff --git a/web/skins/classic/views/class/montage_class.php b/web/skins/classic/views/class/montage_class.php
new file mode 100644
index 0000000000..b941e7190f
--- /dev/null
+++ b/web/skins/classic/views/class/montage_class.php
@@ -0,0 +1,1238 @@
+Id(), но не помогло....
+
+require_once('./includes/Object.php');
+
+class Montage {
+ public static $maxEvents = 200000; //Maximum number of events in the SQL samplek
+ public static $presetLayoutsNames = array( //Order matters!
+ 'Auto',
+ '1 Wide',
+ '2 Wide',
+ '3 Wide',
+ '4 Wide',
+ '6 Wide',
+ '8 Wide',
+ '12 Wide',
+ '16 Wide'
+ );
+ public static $scale = ''; //This remains to be verified
+ public static $layout_is_preset = false;
+ public static $layout = '';
+ public static $layouts = '';
+ public static $layout_id = '';
+ public static $layoutsById = '';
+ public static $request = '';
+ public static $options = [];
+ public static $need_hls = false;
+ public static $need_janus = false;
+ public static $monitors = array();
+ public static $showControl = false;
+ public static $showZones = false;
+ public static $displayMonitors = [];
+ public static $resultMonitorFilters = [];
+ public static $AutoLayoutName = '';
+
+ public function __construct()
+ {
+ $buildLayouts = self::buildLayouts();
+ self::$layout = $buildLayouts['layout'];
+ self::$layout_id = $buildLayouts['layout_id'];
+ self::$layoutsById = $buildLayouts['layoutsById'];
+ self::$layout_is_preset = $buildLayouts['layout_is_preset'];
+ self::$request = $buildLayouts['request'];
+ self::$options = self::buildOptions();
+ self::buildMonitors();
+ }
+
+ public static function implodeWithQuotes(array $data) {
+ //Temporarily not used
+ return sprintf("'%s'", implode("', '", $data));
+ }
+
+ public static function buildLayouts() {
+ require_once('includes/MontageLayout.php');
+
+ if (isset($_REQUEST['showZones'])) {
+ if ($_REQUEST['showZones'] == 1) {
+ self::$showZones = true;
+ }
+ }
+
+ $layouts = ZM\MontageLayout::find(NULL, array('order'=>"lower('Name')"));
+ // layoutsById is used in the dropdown, so needs to be sorted
+ $layoutsById = array();
+
+ /* Create an array "Name"=>layouts to make it easier to find IDs by name */
+ $layoutsByName = array();
+ foreach ($layouts as $l) {
+ if ($l->Name() == 'Freeform') $l->Name('Auto');
+ $layoutsByName[$l->Name()] = $l;
+ }
+
+ /* Fill with preinstalled Layouts. They should always come first.
+ * Also sorting 1 Wide and 11 Wide fails... so need a smarter sort
+ */
+ foreach (self::$presetLayoutsNames as $name) {
+ if (array_key_exists($name, $layoutsByName)) // Layout may be missing in DB (rare case during update process)
+ $layoutsById[$layoutsByName[$name]->Id()] = $layoutsByName[$name];
+ }
+
+ /* Add custom Layouts & assign objects instead of names for preset Layouts */
+ foreach ($layouts as $l) {
+ $layoutsById[$l->Id()] = $l;
+ }
+
+ zm_session_start();
+ $layout_id = 0;
+ if (isset($_REQUEST['zmMontageLayout'])) {
+ $layout_id = $_SESSION['zmMontageLayout'] = validCardinal($_REQUEST['zmMontageLayout']);
+ } else if ( isset($_COOKIE['zmMontageLayout']) ) {
+ $layout_id = $_SESSION['zmMontageLayout'] = validCardinal($_COOKIE['zmMontageLayout']);
+ } else if ( isset($_SESSION['zmMontageLayout']) ) {
+ $layout_id = validCardinal($_SESSION['zmMontageLayout']);
+ }
+ session_write_close();
+ if (!$layout_id || !isset($layoutsById[$layout_id])) {
+ $layout_id = $layoutsByName['Auto']->Id();
+ }
+ $layout = $layoutsById[$layout_id];
+ self::$layout_is_preset = array_search($layout->Name(), self::$presetLayoutsNames) === false ? false : true;
+
+ return [
+ 'layout'=>$layout, //current layout
+ 'layouts'=>$layouts, //all layouts
+ 'layout_id'=>$layout_id, //current layout ID
+ 'layoutsById'=>$layoutsById,
+ 'layout_is_preset'=>self::$layout_is_preset,
+ 'request'=>$_REQUEST
+ ];
+ }
+
+ public static function buildMonitors() {
+ require_once(getSkinFile('views/_monitor_filters.php'));
+
+ /* +++ IMPORTANT !!! This code must be embedded in all files where _monitor_filters.php is used*/
+ $resultMonitorFilters = buildMonitorsFilters();
+ $filterbar = $resultMonitorFilters['filterBar'];
+ $displayMonitors = $resultMonitorFilters['displayMonitors'];
+ /* --- */
+ self::$displayMonitors = $displayMonitors;
+ self::$resultMonitorFilters = $resultMonitorFilters;
+
+ foreach ($displayMonitors as &$row) {
+ //if ($row['Capturing'] == 'None')
+ // continue;
+
+ //$row['Scale'] = $scale;
+ //$row['PopupScale'] = reScale(SCALE_BASE, $row['DefaultScale'], ZM_WEB_DEFAULT_SCALE);
+
+ if (ZM_OPT_CONTROL && $row['ControlId'] && $row['Controllable'])
+ self::$showControl = true;
+ if (!isset($widths[$row['Width'].'px'])) {
+ $widths[$row['Width'].'px'] = $row['Width'].'px';
+ }
+ if (!isset($heights[$row['Height'].'px'])) {
+ $heights[$row['Height'].'px'] = $row['Height'].'px';
+ }
+ $monitor = self::$monitors[] = new ZM\Monitor($row);
+
+ if ( $monitor->RTSP2WebEnabled() and $monitor->RTSP2WebType == "HLS") {
+ self::$need_hls = true;
+ }
+ if ($monitor->JanusEnabled()) {
+ self::$need_janus = true;
+ }
+ } # end foreach Monitor
+
+ $default_layout = '';
+ $monitorCount = count(self::$monitors);
+ if ($monitorCount <= 3) {
+ $default_layout = $monitorCount . ' Wide';
+ } else if ($monitorCount <= 4) {
+ $default_layout = '2 Wide';
+ } else if ($monitorCount <= 6) {
+ $default_layout = '3 Wide';
+ } else if ($monitorCount%4 == 0) {
+ $default_layout = '4 Wide';
+ } else if ($monitorCount%6 == 0) {
+ $default_layout = '6 Wide';
+ } else {
+ $default_layout = '4 Wide';
+ }
+ self::$AutoLayoutName = $default_layout;
+ }
+
+ public static function buildOptions() {
+ $options = array();
+ if (!empty($_REQUEST['maxfps']) and validHtmlStr($_REQUEST['maxfps']) and ($_REQUEST['maxfps']>0)) {
+ $options['maxfps'] = validHtmlStr($_REQUEST['maxfps']);
+ } else if (isset($_COOKIE['zmMontageRate'])) {
+ $options['maxfps'] = validHtmlStr($_COOKIE['zmMontageRate']);
+ } else {
+ $options['maxfps'] = ''; // unlimited
+ }
+ return $options;
+ }
+
+ public static function buildMonitorsFilters() {
+ //Temporarily not used
+ }
+
+ public static function buildGlobalFilters () {
+ //Code moved from \skins\classic\views\montagereview.php
+ global $user;
+
+ $preference = ZM\User_Preference::find_one([
+ 'UserId'=>$user->Id(),
+ 'Name'=>'MontageSort'.(isset($_SESSION['GroupId']) ? implode(',', $_SESSION['GroupId']) : '')
+ ]);
+ if ($preference) {
+ $monitors_by_id = array_to_hash_by_key('Id', $displayMonitors);
+ $sorted_monitors = [];
+ foreach (explode(',', $preference->Value()) as $id) {
+ if (isset($monitors_by_id[$id])) {
+ $sorted_monitors[] = $monitors_by_id[$id];
+ } else {
+ ZM\Debug("Ordered monitor not found in monitorsById $id");
+ }
+ }
+ if (count($sorted_monitors)) $displayMonitors = $sorted_monitors;
+ }
+
+ // Parse input parameters -- note for future, validate/clean up better in case we don't get called from self.
+ // Live overrides all the min/max stuff but it is still processed
+
+ // The default (nothing at all specified) is for 1 hour so we do not read the whole database
+
+ if (isset($_REQUEST['current'])) {
+ $defaultCurrentTime = validHtmlStr($_REQUEST['current']);
+ $defaultCurrentTimeSecs = strtotime($defaultCurrentTime);
+ }
+
+ if ( !isset($_REQUEST['minTime']) && !isset($_REQUEST['maxTime']) ) {
+ if (isset($defaultCurrentTimeSecs)) {
+ $minTime = date('Y-m-d H:i:s', $defaultCurrentTimeSecs - 1800);
+ $maxTime = date('Y-m-d H:i:s', $defaultCurrentTimeSecs + 1800);
+ } else {
+ $time = time();
+ $maxTime = date('Y-m-d H:i:s', $time);
+ $minTime = date('Y-m-d H:i:s', $time - 3600);
+ }
+ } else {
+ if (isset($_REQUEST['minTime']))
+ $minTime = validHtmlStr($_REQUEST['minTime']);
+
+ if (isset($_REQUEST['maxTime']))
+ $maxTime = validHtmlStr($_REQUEST['maxTime']);
+ }
+
+ // AS a special case a "all" is passed in as an extreme interval - if so, clear them here and let the database query find them
+
+ if ( (strtotime($maxTime) - strtotime($minTime))/(365*24*3600) > 30 ) {
+ // test years
+ $minTime = null;
+ $maxTime = null;
+ }
+
+ $filter = null;
+ if (isset($_REQUEST['filter'])) {
+ $filter = ZM\Filter::parse($_REQUEST['filter']);
+ $terms = $filter->terms();
+
+ # Try to guess min/max time from filter
+ foreach ($terms as &$term) {
+ if ($term['attr'] == 'Notes') {
+ $term['cookie'] = 'Notes';
+ if (empty($term['val']) and isset($_COOKIE['Notes'])) $term['val'] = $_COOKIE['Notes'];
+ } else if ($term['attr'] == 'StartDateTime') {
+ if ($term['op'] == '<=' or $term['op'] == '<') {
+ $maxTime = $term['val'];
+ $term['id'] = 'EndDateTime';
+ } else if ( $term['op'] == '>=' or $term['op'] == '>' ) {
+ $minTime = $term['val'];
+ $term['id'] = 'StartDateTime';
+ }
+ }
+ } # end foreach term
+ $filter->terms($terms);
+ } else {
+ $filter = new ZM\Filter();
+ if (isset($_REQUEST['minTime']) && isset($_REQUEST['maxTime']) && (count($displayMonitors) != 0)) {
+ $filter->addTerm(array('id' => 'StartDateTime', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => $_REQUEST['minTime'], 'obr' => '1'));
+ $filter->addTerm(array('id' => 'EndDateTime', 'attr' => 'StartDateTime', 'op' => '<=', 'val' => $_REQUEST['maxTime'], 'cnj' => 'and', 'cbr' => '1'));
+ if (count($selected_monitor_ids)) {
+ $filter->addTerm(array('attr' => 'Monitor', 'op' => 'IN', 'val' => implode(',',$selected_monitor_ids), 'cnj' => 'and'));
+ } else if ( isset($_SESSION['GroupId']) || isset($_SESSION['ServerFilter']) || isset($_SESSION['StorageFilter']) || isset($_SESSION['StatusFilter']) ) {
+ # this should be redundant
+ for ( $i = 0; $i < count($displayMonitors); $i++ ) {
+ if ( $i == '0' ) {
+ $filter->addTerm(array('attr' => 'MonitorId', 'op' => '=', 'val' => $displayMonitors[$i]['Id'], 'cnj' => 'and', 'obr' => '1'));
+ } else if ( $i == (count($displayMonitors)-1) ) {
+ $filter->addTerm(array('attr' => 'MonitorId', 'op' => '=', 'val' => $displayMonitors[$i]['Id'], 'cnj' => 'or', 'cbr' => '1'));
+ } else {
+ $filter->addTerm(array('attr' => 'MonitorId', 'op' => '=', 'val' => $displayMonitors[$i]['Id'], 'cnj' => 'or'));
+ }
+ }
+ }
+ } # end if REQUEST[Filter]
+ }
+ if (!$filter->has_term('Archived')) {
+ $filter->addTerm(array('id' => 'Archived', 'attr' => 'Archived', 'op' => '=', 'val' => '', 'cnj' => 'and'));
+ }
+ if (!$filter->has_term('StartDateTime', '>=')) {
+ $filter->addTerm(array('id' => 'StartDateTime', 'attr' => 'StartDateTime', 'op' => '>=', 'cnj' => 'and'));
+ }
+ if (!$filter->has_term('StartDateTime', '<=')) {
+ $filter->addTerm(array('id' => 'EndDateTime', 'attr' => 'StartDateTime', 'op' => '<=', 'cnj' => 'and'));
+ }
+ if (!$filter->has_term('Tags')) {
+ $filter->addTerm(array('id' => 'Tags', 'attr' => 'Tags', 'op' => '=',
+ 'val' => (isset($_COOKIE['eventsTags']) ? $_COOKIE['eventsTags'] : ''),
+ 'cnj' => 'and', 'cookie'=>'eventsTags'));
+ }
+ if (!$filter->has_term('Notes')) {
+ $filter->addTerm(array('id' => 'Notes', 'cnj'=>'and', 'attr'=>'Notes', 'op'=> 'LIKE', 'val'=>'', 'cookie'=>'eventsNotes'));
+ }
+ if (count($filter->terms()) ) {
+ # This is to enable the download button
+ zm_session_start();
+ $_SESSION['montageReviewFilter'] = $filter;
+ session_write_close();
+ }
+ return $filter;
+ }
+
+ public static function setScale($scale) {
+ self::$scale = $scale;
+ }
+
+ /*
+ * $width - monitor width "$monitor->Width()" or event
+ */
+ public static function scaleCalculation($width, $scale='') {
+ $layout = self::$layout;
+ $newScale = '';
+ if (!$width || (int)$width == 0) return $newScale;
+
+ if (!$scale and ($layout->Name() != 'Auto')) {
+ if (self::$layout_is_preset) {
+ # We know the # of columns so can figure out a proper scale
+ if (preg_match('/^(\d+) Wide$/', $layout->Name(), $matches)) {
+ if ($matches[1]) {
+ $newScale = intval(100*((1920/$matches[1])/$width));
+ //if ($newScale > 100) $newScale = 100;
+ }
+ }
+ } else {
+ # Custom, default to 25% of 1920 for now, because 25% of a 4k is very different from 25% of 640px
+ $newScale = intval(100*((1920/4)/$width));
+ //if ($newScale > 100) $newScale = 100;
+ }
+ if ($newScale > 100) $newScale = 100;
+ }
+ return $newScale;
+ }
+
+ public static function buildGridMonitorsLive() {
+ // This code is moved from skins/classic/views/montage.php
+
+ $blockMonitors = "";
+ foreach (self::$monitors as $monitor) {
+ if ($monitor->Capturing() == 'None')
+ continue;
+ $monitor_options = self::$options;
+ #ZM\Debug('Options: ' . print_r($monitor_options,true));
+
+ if ($monitor->Type() == 'WebSite') {
+ echo getWebSiteUrl(
+ 'liveStream'.$monitor->Id(),
+ $monitor->Path(),
+ (isset($monitor_options['width']) ? $monitor_options['width'] : reScale($monitor->ViewWidth(), $scale).'px' ),
+ (isset($monitor_options['height']) ? $monitor_options['height'] : reScale($monitor->ViewHeight(), $scale).'px' ),
+ $monitor->Name()
+ );
+ } else {
+ $monitor_options['state'] = !ZM_WEB_COMPACT_MONTAGE;
+ $monitor_options['zones'] = self::$showZones;
+ $monitor_options['mode'] = 'paused';
+
+ $monitor_options['scale'] = self::scaleCalculation($monitor->Width());
+
+ $blockMonitors .= $monitor->getStreamHTML($monitor_options);
+ }
+ } # end foreach monitor
+
+ return ['monitors'=>$blockMonitors];
+ }
+
+ public static function buildGridMonitorsInRecords($dateTime) {
+ // Соберем массив с событиями-заглушками для отображения хоть чего-то.
+ $monitorsId = [];
+ foreach (self::$monitors as $monitor) {
+ $monitorsId[] = $monitor->Id();
+ }
+ //Выбираем последние события.
+ $lastEvents = self::queryEvents($filter='', $monitorsId, $startDateTime = $dateTime, $endDateTime='', $resolution=0, $action='queryEventsForMonitor', $actionRange='last', $maxFPS = null);
+
+
+/* +++++ IgorA100 ВРЕМЕННО ПЕРЕНЕСЕНО для работы со слоями.... */
+ $scale = ''; //IgorA100 Временно, что бы варнинга PHP не было, а вообще мы эту переменную не используем уже и еще... :)
+/* ----- IgorA100 ВРЕМЕННО ПЕРЕНЕСЕНО для работы со слоями.... */
+ $layout = self::$layout;
+ $layout_is_preset = self::$layout_is_preset;
+ $blockMonitors = "";
+ foreach (self::$monitors as $monitor) {
+ $lastEventForMonitor = self::findDataInEventsArray($monitor->Id(), $lastEvents['events']);
+ if (!$lastEventForMonitor) {
+ //Т.к. нет записанных событий для монитора, то монитор не отображаем.
+ continue;
+ }
+ $monitor_options = self::$options;
+ $monitor_options['lastEvent'] = $lastEventForMonitor;
+ #ZM\Debug('Options: ' . print_r($monitor_options,true));
+
+ if ($monitor->Type() == 'WebSite') {
+ echo getWebSiteUrl(
+ 'liveStream'.$monitor->Id(),
+ $monitor->Path(),
+ //(isset($options['width']) ? $options['width'] : reScale($monitor->ViewWidth(), $scale).'px' ),
+ //(isset($options['height']) ? $options['height'] : reScale($monitor->ViewHeight(), $scale).'px' ),
+ reScale($monitor->ViewWidth(), $scale).'px',
+ reScale($monitor->ViewHeight(), $scale).'px',
+ $monitor->Name()
+ );
+ } else {
+ $monitor_options['state'] = !ZM_WEB_COMPACT_MONTAGE;
+ $monitor_options['zones'] = self::$showZones;
+ $monitor_options['mode'] = 'paused';
+ if (!$scale and ($layout->Name() != 'Auto')) {
+ if ($layout_is_preset) {
+ # We know the # of columns so can figure out a proper scale
+ if (preg_match('/^(\d+) Wide$/', $layout->Name(), $matches)) {
+ if ($matches[1]) {
+ $monitor_options['scale'] = intval(100*((1920/$matches[1])/$monitor->Width()));
+ if ($monitor_options['scale'] > 100) $monitor_options['scale'] = 100;
+ }
+ }
+ } else {
+ # Custom, default to 25% of 1920 for now, because 25% of a 4k is very different from 25% of 640px
+ $monitor_options['scale'] = intval(100*((1920/4)/$monitor->Width()));
+ if ($monitor_options['scale'] > 100) $monitor_options['scale'] = 100;
+ }
+ }
+
+ //$blockMonitors .= $monitor->getStreamHTML($monitor_options);
+ $monitor_options['frameId'] = 'FrameID'; //НУЖНО ПОДСТАВЛЯТЬ ID Фрейма !!!
+ $monitor_options['monitorId'] = $monitor->Id();
+
+ $blockMonitors .= self::buildEventHTML($monitor_options);
+ }
+ } # end foreach monitor
+//echo $blockMonitors;
+ //return ['monitors'=>$blockMonitors, 'lastEvents'=>$lastEvents, 'lastEvents_events'=>$lastEvents['events'], 'lastEvent!!!'=>self::findDataInEventsArray(15, $lastEvents['events'])];
+ return ['monitors'=>$blockMonitors, 'lastEvents'=>$lastEvents];
+ }
+
+ public static function getEventInfoHTML($monitor) {
+ //IgorA100 Перенесено из includes\Monitor.php со значительными изменениями
+ //Пересмотреть отображаемые данные
+
+ $monitorId = $monitor->Id();
+ $monitorName = $monitor->Name();
+ //$monitorStatus = $monitor->Status(); //Получим WARNING, если монитора не будет в таблице 'Monitor_Status'
+ // А его там может не быть, т.к. например его отключили, но события по нем есть.
+ //$monitorStatus = '';
+ $monitorAnalysing = $monitor->Analysing();
+/*
+Cause: "Continuous"
+EndDateTime: "2024-07-19 14:30:20"
+Frames: 9005
+Id: 509973
+Length: "600.08"
+MonitorId: 6
+StartDateTime: "2024-07-19 14:20:20"
+Width: 1920
+*/
+ $html = '
+
+
'.$monitorName.' (id='.$monitorId.')
+
+ '.translate('Event ID').':'.''.'
+ fps
+ '.translate('Cause').':
+ '.translate('Length').':
+ '.translate('Width').': px
+ '.translate('Frames').':
+
'.translate('Start time').':
+
'.translate('End time').':
+
+
+';
+
+/*
+ $html = '
+
+
'.$monitorName.' (id='.$monitorId.')
+
+ '.translate('State').':'.$monitorStatus.'
+ fps
+ fps
+';
+ if ($monitorAnalysing != 'None') {
+ $html .= ' fps
+ ';
+ }
+ $html .= '
+ '.translate('Rate').': x
+ '.translate('Delay').': s
+ '.translate('Buffer').': %
+ '. translate('Zoom').': x
+
+
+';
+*/
+ return $html;
+ }
+
+ public static function buildEventHTML($options) {
+ //ЭТО ПОТОМ, здесь СОБЫТИЕ НЕ ГРУЗИМ !!!
+ //IgorA100 Перенесено из includes\Monitor.php со значительными изменениями
+ global $basename;
+ $basename = "montage"; //Временно....
+
+ //Часть кода взята из skins\classic\views\event.php
+ require_once('includes/Event.php');
+ require_once('includes/Event_Data.php');
+ require_once('includes/Filter.php');
+ require_once('includes/Zone.php');
+
+ //Монитор используем для расчета ширины и высоты (наложение зон детекции). ?????Конечно нужно смотреть размеры события, но у нас их пока нет.
+ //ВРЕМЕННО!!! Поэтому ориентируемся пока на монитор.
+ $monitorId = $options['monitorId'];
+// $monitorId = 5;
+ $monitor = new ZM\Monitor($monitorId);
+
+ //$Event = new ZM\Event($eid);
+
+ if (!$monitor->canView()) {
+ $view = 'error';
+ return;
+ }
+
+
+ $blockRatioControl = ($basename == "montage") ? '' : '';
+ $html = '
+
+ ' . $blockRatioControl . '
+
+
+
+
+
+
+
';
+ $html .= '
+
+
';
+
+ //$streamSrc = 'no data'; //Когда только формируем страницу, событий еще нет...
+ $eid = $options['lastEvent']['Id'];
+ //
+ $Event = new ZM\Event($eid);
+ $fid = null;
+ if (file_exists($Event->Path().'/alarm.jpg') && filesize($Event->Path().'/alarm.jpg') > 0) {
+ $fid = 'alarm';
+ } else if (file_exists($Event->Path().'/objdetect.jpg') && filesize($Event->Path().'/objdetect.jpg') > 0) {
+ $fid = 'objdetect';
+ } else if (file_exists($Event->Path().'/snapshot.jpg') && filesize($Event->Path().'/snapshot.jpg') > 0) {
+ $fid = 'snapshot';
+ } else {
+ //!!! IMPORTANT It is necessary to check the file with the event! It may not be there due to a failure. But something needs to be displayed!
+ if ($Event->file_exists() && $Event->file_size() > 0) {
+ require_once('includes/Frame.php');
+ $numFrame = 1; //Номер фрейма, который получаем.
+ $Frame = ZM\Frame::find_one(array('EventId'=>$eid, 'FrameId'=>$numFrame));
+ if ($Frame) {
+ $fid = $numFrame;
+ }
+ }
+ }
+ $width = intval($options['lastEvent']['Width'] / 100 * $options['scale']);
+ if ($fid) {
+ $streamSrc = '?view=image&eid='. $eid.'&fid='.$fid.'&width='.$width;
+ } else {
+ $streamSrc = '?view=image&path='. 'graphics/no-frame.jpg'.'&width='.$width;
+ }
+ //!!! ВАЖНО В данных о событиях есть Height, Width, значит можно рассчитать пропорции и выводить ЗАГЛУШКУ с высотой, что бы не портить расположение мониторов на экране.
+
+ //src="http://192.168.111.244:30006/zm/cgi-bin/nph-zms?mode=jpeg&frame=8595703890&scale=51&rate=100&maxfps=5&replay=none&source=event&event=437496&rand=1719528464&auth=ad5fc1d3b184e2f4c100e1a60f7a5a80"
+ //src="http://192.168.111.244:30006/zm/cgi-bin/nph-zms?mode=single&frame=8595703890&scale=51&rate=100&maxfps=5&replay=none&source=event&event=437496&rand=1719528464&auth=ad5fc1d3b184e2f4c100e1a60f7a5a80"
+ //$streamSrc = $Event->getStreamSrc(array('mode'=>'jpeg', 'frame'=>$fid, 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>$maxFPS, 'replay'=>$replayMode),'&');
+ //НЕ ЗАБЫТЬ убрать анализ "if (decodeURI(img.src).indexOf('no data') !== -1) {" из montage.js !!!
+ //Но надо что-то выводить, например последний фрейм последнего события.
+ $html .= getImageStreamHTML('evtStream'.$monitorId, $streamSrc,
+// ($scale ? reScale($Event->Width(), $scale).'px' : '100%'),
+// ($scale ? reScale($Event->Height(), $scale).'px' : 'auto'),
+ 'auto',
+ 'auto',
+// validHtmlStr($Event->Name()));
+ '');
+
+
+
+
+
+
+
+
+
+ if (isset($options['zones']) and $options['zones']) {
+// $html .= '
'.PHP_EOL;
+ if (isset($options['state']) and $options['state']) {
+ //if ((!ZM_WEB_COMPACT_MONTAGE) && ($this->Type() != 'WebSite')) {
+ $html .= self::getEventInfoHTML($monitor);
+ }
+
+ $html .= PHP_EOL.'
'.PHP_EOL;
+ return $html;
+ } // end buildEventHTML
+
+ public static function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) {
+ //Temporarily not used
+ //Part of the code was moved from usr\share\zoneminder\www\ajax\events.php
+ /*
+ global $dateTimeFormatter;
+ $data = array(
+ 'total' => 0,
+ 'totalNotFiltered' => 0,
+ 'rows' => array(),
+ 'updated' => $dateTimeFormatter->format(time())
+ );
+
+ if (!$filter->test_pre_sql_conditions()) {
+ ZM\Debug('Pre conditions failed, not doing sql');
+ return $data;
+ }
+
+ // Put server pagination code here
+ // The table we want our data from
+ $table = 'Events';
+
+ // The names of the dB columns in the events table we are interested in
+ $columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartDateTime', 'EndDateTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace');
+
+ // The names of columns shown in the event view that are NOT dB columns in the database
+ $col_alt = array('Monitor', 'Tags', 'Storage');
+
+ if ( $sort != '' ) {
+ if (!in_array($sort, array_merge($columns, $col_alt))) {
+ ZM\Error('Invalid sort field: ' . $sort);
+ $sort = '';
+ } else if ( $sort == 'Tags' ) {
+ $sort = 'T.Name';
+ } else if ( $sort == 'Monitor' ) {
+ $sort = 'M.Name';
+ } else if ($sort == 'EndDateTime') {
+ if ($order == 'ASC') {
+ $sort = 'E.EndDateTime IS NULL, E.EndDateTime';
+ } else {
+ $sort = 'E.EndDateTime IS NOT NULL, E.EndDateTime';
+ }
+ } else {
+ $sort = 'E.'.$sort;
+ }
+ }
+
+ $values = array();
+ $likes = array();
+ // ZM\Error($filter->sql());
+ $where = $filter->sql()?' WHERE ('.$filter->sql().')' : '';
+ $has_post_sql_conditions = count($filter->post_sql_conditions());
+
+
+ $col_str = '
+ E.*,
+ UNIX_TIMESTAMP(E.StartDateTime) AS StartTimeSecs,
+ CASE WHEN E.EndDateTime IS NULL THEN (SELECT NOW()) ELSE E.EndDateTime END AS EndDateTime,
+ CASE WHEN E.EndDateTime IS NULL THEN (SELECT UNIX_TIMESTAMP(NOW())) ELSE UNIX_TIMESTAMP(EndDateTime) END AS EndTimeSecs,
+ M.Name AS Monitor,
+ GROUP_CONCAT(T.Name SEPARATOR ", ") AS Tags';
+
+ $sql = 'SELECT '.$col_str.' FROM `Events` AS E
+ INNER JOIN Monitors AS M ON E.MonitorId = M.Id
+ LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
+ LEFT JOIN Tags AS T ON T.Id = ET.TagId
+ '.$where.'
+ GROUP BY E.Id
+ '.($sort?' ORDER BY '.$sort.' '.$order:'');
+
+ if ((int)($filter->limit()) and !$has_post_sql_conditions) {
+ $sql .= ' LIMIT '.(int)($filter->limit());
+ }
+
+ $storage_areas = ZM\Storage::find();
+ $StorageById = array();
+ foreach ($storage_areas as $S) {
+ $StorageById[$S->Id()] = $S;
+ }
+
+ $unfiltered_rows = array();
+ $event_ids = array();
+
+ ZM\Debug('Calling the following sql query: ' .$sql);
+ $query = dbQuery($sql, $values);
+ if (!$query) {
+ ajaxError(dbError($sql));
+ return;
+ }
+ while ($row = dbFetchNext($query)) {
+ if ($has_post_sql_conditions) {
+ $event = new ZM\Event($row);
+ $event->remove_from_cache();
+ if (!$filter->test_post_sql_conditions($event)) {
+ continue;
+ }
+ }
+ $event_ids[] = $row['Id'];
+ $unfiltered_rows[] = $row;
+ } # end foreach row
+
+ # Filter limits come before pagination limits.
+ if ($filter->limit() and ($filter->limit() > count($unfiltered_rows))) {
+ ZM\Debug("Filtering rows due to filter->limit " . count($unfiltered_rows)." limit: ".$filter->limit());
+ $unfiltered_rows = array_slice($unfiltered_rows, 0, $filter->limit());
+ }
+
+ ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.');
+
+ $filtered_rows = null;
+
+ if (count($advsearch) or $search != '') {
+ $search_filter = new ZM\Filter();
+ $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids));
+
+ // There are two search bars in the log view, normal and advanced
+ // Making an exuctive decision to ignore the normal search, when advanced search is in use
+ // Alternatively we could try to do both
+ if (count($advsearch)) {
+ $terms = array();
+ foreach ($advsearch as $col=>$text) {
+ $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text);
+ } # end foreach col in advsearch
+ $terms[0]['obr'] = 1;
+ $terms[count($terms)-1]['cbr'] = 1;
+ $search_filter->addTerms($terms);
+ } else if ($search != '') {
+ $search = '%' .$search. '%';
+ $terms = array();
+ foreach ($columns as $col) {
+ $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>strtolower($search), 'collate'=>'utf8mb4_general_ci');
+ }
+ $terms[0]['obr'] = 1;
+ $terms[0]['cnj'] = 'and';
+ $terms[count($terms)-1]['cbr'] = 1;
+ $search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR'));
+ } # end if search
+
+ $sql = 'SELECT '.$col_str.' FROM `Events` AS E
+ INNER JOIN Monitors AS M ON E.MonitorId = M.Id
+ LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
+ LEFT JOIN Tags AS T ON T.Id = ET.TagId
+ WHERE '.$search_filter->sql().'
+ GROUP BY E.Id
+ ORDER BY ' .$sort. ' ' .$order;
+
+ $filtered_rows = dbFetchAll($sql);
+ ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql);
+ } else {
+ $filtered_rows = $unfiltered_rows;
+ } # end if search_filter->terms() > 1
+
+ if ($limit and ($limit < count($filtered_rows))) {
+ ZM\Debug("Filtering rows due to limit rows: " . count($filtered_rows)." offset: $offset limit: $limit");
+ $filtered_rows = array_slice($filtered_rows, $offset, $limit);
+ }
+
+ $returned_rows = array();
+ foreach ($filtered_rows as $row) {
+ $event = new ZM\Event($row);
+ $event->remove_from_cache();
+ if (!$event->canView()) continue;
+ if ($event->Monitor()->Deleted()) continue;
+
+ $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());
+ $imgSrc = $event->getThumbnailSrc(array(), '&');
+ $streamSrc = $event->getStreamSrc(array(
+ 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&');
+ //$streamSrc = $event->getStreamSrc(array(
+ // 'mode'=>'mpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&');
+
+ // Modify the row data as needed
+ $row['imgHtml'] = '';
+ $row['imgWidth'] = validInt($event->ThumbnailWidth());
+ $row['imgHeight'] = validInt($event->ThumbnailHeight());
+
+ $row['Name'] = validHtmlStr($row['Name']);
+ $row['Archived'] = $row['Archived'] ? translate('Yes') : translate('No');
+ $row['Emailed'] = $row['Emailed'] ? translate('Yes') : translate('No');
+ $row['Cause'] = validHtmlStr($row['Cause']);
+ $row['Tags'] = validHtmlStr($row['Tags']);
+ $row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default';
+ $row['Notes'] = nl2br(htmlspecialchars($row['Notes']));
+ $row['DiskSpace'] = human_filesize($event->DiskSpace());
+ $returned_rows[] = $row;
+ } # end foreach row matching search
+
+ $data['rows'] = &$returned_rows;
+
+ # totalNotFiltered must equal total, except when either search bar has been used
+ $data['totalNotFiltered'] = count($unfiltered_rows);
+ if ( $search != '' || count($advsearch) ) {
+ $data['total'] = count($filtered_rows);
+ } else {
+ $data['total'] = $data['totalNotFiltered'];
+ }
+ ZM\Debug("Done");
+ return $data;
+ */
+ }
+
+ /*
+ * Getting the minimum start date for event recording for a group of monitors
+ */
+ public static function queryMinData($filter, $monitorsId) {
+ $sqlString = self::buildQueryString($filter, $monitorsId, $startDateTime='', $endDateTime='', $actionRange='minData', $fullResponse=false);
+ $result = dbFetch($sqlString, $col='minData')[0];
+ return $result;
+ }
+
+ /*
+ * $actionRange - 'range', 'first', 'last', 'prev', 'next'
+ * $endDateTime - only required for $actionRange = 'range'
+ * $maxFPS - FPS at which the video will be played.
+ */
+ public static function queryEvents($filter, $monitorsId, $startDateTime, $endDateTime, $resolution, $action, $actionRange, $maxFPS = null) {
+ //Part of the code has been moved from ajax\events.php
+
+ if (!$maxFPS) {
+ $maxFPS = self::$options['maxfps'];
+ }
+
+ $startDateTimeSec = strtotime($startDateTime);
+ $endDateTimeSec = strtotime($endDateTime);
+ //require_once('includes/Filter.php');
+ $currentTimeEventMonitors = []; //Store the end time of the event for the current monitor of their SQL sample, so that $resolution can be used to exclude nearby events
+
+ $fullResponse = ($action == 'queryEventsForMonitor' && $actionRange == 'last') ? true : false;
+
+ $eventsSql = self::buildQueryString($filter, $monitorsId, $startDateTime, $endDateTime, $actionRange, $fullResponse);
+
+
+ $EventsById = array();
+ $longEvents = array();
+ $streamSrc = array();
+
+ $events = dbFetchAll($eventsSql);
+ $result = [];
+$start_ = microtime(true);
+
+ $i = 1;
+ //if ($resQuery) {
+ if ($events) {
+ $count_array = count($events);
+ //$resolutionNew = $resolution * ($count_array / 3000);
+ if ($endDateTimeSec - $startDateTimeSec < (60 * 60 * 3)) {
+ //В Timeline отображается 3х часовой промежуток
+ $resolutionNew = 1;
+ } else if ($endDateTimeSec - $startDateTimeSec < (60 * 60 * 24)) {
+ //В Timeline отображается суточный промежуток
+ $resolutionNew = $resolution * 10;
+ } else if ($endDateTimeSec - $startDateTimeSec < (60 * 60 * 24 * 30)) {
+ //В Timeline отображается месячный промежуток
+ $resolutionNew = $resolution * 20;
+ } else {
+ $resolutionNew = $resolution * 30;
+ }
+ if ($count_array < self::$maxEvents) {
+ //Ограничим выборку
+ $result['tooManyEvents'] = false;
+ for($i = 0; $i < $count_array; $i++) {
+ $event = $events[$i];
+ if ($action == 'queryNextEventForMonitor') {
+ //Не требуется дополнительных проверок.
+ $addEvent = true;
+ } else {
+ //$eventEndTimeSecs = strtotime($event['EndDateTime']);
+ //$eventStartTimeSecs = strtotime($event['StartDateTime']);
+ $eventEndTimeSecs = $event['EndTimeSecs'];
+ $eventStartTimeSecs = $event['StartTimeSecs'];
+ $addEvent = false;
+ if (!isset($currentTimeEventMonitors[$event['MonitorId']])) {
+ //Первое событие для монитора.
+ $addEvent = true;
+ $currentTimeEventMonitors[$event['MonitorId']] = 1;
+ } else if ($eventEndTimeSecs > ($resolutionNew) + $currentTimeEventMonitors[$event['MonitorId']] ) {
+ //} else if ($eventStartTimeSecs > ($resolutionNew) + $currentTimeEventMonitors[$event['MonitorId']] ) {
+ // Прореживаем последующие события.
+ $addEvent = true;
+ $currentTimeEventMonitors[$event['MonitorId']] = $eventEndTimeSecs;
+ } else if ($event['Archived']) {
+ //Архивные выводим все.
+ $addEvent = true;
+ $currentTimeEventMonitors[$event['MonitorId']] = $eventEndTimeSecs;
+ } else {
+ }
+ }
+ if ($addEvent) {
+ if ($action == 'queryEventsForMonitor' || $action == 'queryNextEventForMonitor') {
+ //Необходимо собрать SRC строку
+ //$eid = 427483; //Id камеры = 8
+ $eid = $event['Id'];
+ $objEvent = new ZM\Event($eid);
+ $monitor = $objEvent->Monitor();
+ /* +++ ВРЕМЕННО */
+ $rate = reScale(RATE_BASE, $monitor->DefaultRate(), ZM_WEB_DEFAULT_RATE);
+ $replayMode = 'none';
+ /* --- ВРЕМЕННО */
+ $scale = self::scaleCalculation($event['Width']);
+ //Найдем FID с которого запустить просмотр.
+ //Сбойное событие может не иметь длинну! В этом случае считаем FPS=1, что бы не было ошибки при делении.
+ $FPSEvent = ($event['Length'] > 0) ? $event['Frames'] / $event['Length'] : 1;
+ if ($action == 'queryNextEventForMonitor') {
+ $fid = 1;
+ } else {
+ $fid = intval($FPSEvent * ($startDateTimeSec - $event['StartTimeSecs']));
+ }
+// $streamSrc[$eid] = $objEvent->getStreamSrc(array('mode'=>'jpeg', 'frame'=>$fid, 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>$replayMode),'&');
+// $streamSrc[$eid] = $objEvent->getStreamSrc(array('mode'=>'jpeg', 'frame'=>$fid, 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>$replayMode),'&');
+ if (!$maxFPS || $maxFPS == 'Unlimited') $maxFPS = ZM_WEB_VIDEO_MAXFPS;
+ /*РАБОЧИЙ вариант*/ //$streamSrc[$eid] = $objEvent->getStreamSrc(array('mode'=>'jpeg', 'frame'=>$fid, 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>$maxFPS, 'replay'=>$replayMode, 'connkey'=>generateConnKey()),'&');
+ /*БЕЗ FID*/ $streamSrc[$eid] = $objEvent->getStreamSrc(array('mode'=>'jpeg', 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>$maxFPS, 'replay'=>$replayMode, 'connkey'=>generateConnKey()),'&');
+ /*Попытка через TIME*/ //$streamSrc[$eid] = $Event->getStreamSrc(array('mode'=>'jpeg', 'time'=>$startDateTimeSec, 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>$maxFPS, 'replay'=>$replayMode, 'connkey'=>generateConnKey()),'&');
+ //$streamSrc[$eid] = $objEvent->getStreamSrc(array('mode'=>'mpeg', 'frame'=>$fid, 'scale'=>($scale > 0 ? $scale : 100), 'rate'=>$rate, 'maxfps'=>$maxFPS, 'replay'=>$replayMode),'&');
+ }
+ unset($event['StartTimeSecs']); //Уменьшим передаваемых данных
+ unset($event['EndTimeSecs']);
+ $EventsById[] = $event;
+ }
+ }
+ } else {
+ $result['tooManyEvents'] = true;
+ }
+ }
+
+/*
+ if (is_array($filter)) {
+ $filter['QQQ'] = ["df, sad,выап", "aaaaaaa"];
+ foreach ($filter as $name => $value) { //ДЛЯ ТЕСТА
+ $result['filter'.$name] = $value;
+ if (is_array($value)) {
+ $result['__filter'.$name] = self::implodeWithQuotes($value);
+ } else {
+ $result['__filter'.$name] = $value;
+ }
+ }
+ } else {
+ //$result['filter_STRING'] = $value;
+ }
+*/
+ if (isset($FPSEvent)) $result['FPSEvent'] = $FPSEvent; //ДЛЯ ТЕСТА
+ $result['filter'] = $filter; //ДЛЯ ТЕСТА
+ $result['_action'] = $action; //ДЛЯ ТЕСТА
+ $result['_actionRange'] = $actionRange; //ДЛЯ ТЕСТА
+ $result['eventsSql'] = $eventsSql; //ДЛЯ ТЕСТА
+// $result['SQL'] = $events; //ДЛЯ ТЕСТА
+ $result['SQL_COUNT'] = count($events); //ДЛЯ ТЕСТА
+ $result['actionRange'] = $actionRange; //ДЛЯ ТЕСТА
+// $result['->fetch->event'] = $resQuery->fetch(PDO::FETCH_ASSOC); //ДЛЯ ТЕСТА
+ $result['startDateTime'] = $startDateTime; //ДЛЯ ТЕСТА ???
+ $result['endDateTime'] = $endDateTime; //ДЛЯ ТЕСТА ???
+ $result['deltaDateTimeSec'] = $endDateTimeSec - $startDateTimeSec; //ДЛЯ ТЕСТА ???
+ $result['resolution'] = $resolution; //ДЛЯ ТЕСТА ???
+ //$result['event_Length'] = $event['Length']; //ДЛЯ ТЕСТА ???
+ //$result['event_Frames'] = $event['Frames']; //ДЛЯ ТЕСТА ???
+ $result['_microtime'] = microtime(true) - $start_; //ДЛЯ ТЕСТА
+ $result['currentTimeEventMonitors'] = $currentTimeEventMonitors; //ДЛЯ ТЕСТА ???
+
+ $result['event'] = (isset($event)) ? $event : null; //ЗАЧЕМ??? Может не используем ???
+ $result['actionRange'] = $actionRange;
+ $result['streamSrc'] = &$streamSrc;
+ $result['events'] = &$EventsById;
+ $result['allEventCount'] = count($events);
+
+ return $result;
+ }
+
+ /*
+ * $actionRange - 'range', 'first', 'last', 'prev', 'next', 'minData'
+ * $endDateTime - only required for $actionRange = 'range'
+ * $fullResponse - if 'true' then the response will contain all columns of the table. This will SLOW DOWN the query! For now it is only used when receiving the last event for the monitor.
+ */
+ public static function buildQueryString($filter, array $monitorsId=[], $startDateTime, $endDateTime, string $actionRange, bool $fullResponse=false) {
+ $oneMonitors = (count($monitorsId) == 1) ? true : false;
+ $where = ' WHERE';
+ if (count($monitorsId) == 0) {
+ $where .= ' 1=1';
+ } else {
+ $where .= ($oneMonitors) ? ' E.MonitorId='.$monitorsId[0] : ' (E.MonitorId IN ('.implode(',', $monitorsId).'))';
+ }
+ $select = 'SELECT';
+ $join = '';
+ $from = ' FROM Events AS E';
+ $group = '';
+ $order = '';
+ $limit = '';
+
+/*
+++++++++++++++++
+ SELECT E.Id, E.MonitorId, E.StartDateTime, E.EndDateTime, E.Cause, E.Length, E.Frames, E.Width, E.Archived
+ ,UNIX_TIMESTAMP(E.StartDateTime) AS StartTimeSecs,
+ #CASE WHEN E.EndDateTime IS NULL THEN NOW() ELSE E.EndDateTime END AS _EndDateTime,
+ CASE WHEN E.EndDateTime IS NULL THEN UNIX_TIMESTAMP(NOW()) ELSE UNIX_TIMESTAMP(E.EndDateTime) END AS EndTimeSecs
+ FROM Events AS E
+
+ INNER JOIN (
+ SELECT
+ MonitorId,
+ MAX(Id) AS LastEvent
+ FROM
+ Events
+ GROUP BY
+ MonitorId) AS top ON (
+ E.MonitorId = top.MonitorId
+ )
+ WHERE (E.MonitorId IN (5,6,22,33,37))
+ AND (
+ (E.Id = top.LastEvent AND E.EndDateTime IS NULL AND E.StartDateTime BETWEEN '2024-10-14 00:00:00' AND '2024-10-14 00:59:00')
+ OR
+ (E.EndDateTime >='2024-10-14 00:00:00' AND E.StartDateTime <='2024-10-14 00:59:00')
+ )
+ ORDER BY E.StartDateTime ASC
+ -------------
+*/
+
+ if ($actionRange == 'range') {
+ #SELECT Id, MonitorId, StartDateTime, EndDateTime, Cause
+ #FROM Events
+ #WHERE EndDateTime > '2024-06-18 10:08:57'
+ #AND StartDateTime < '2024-06-20 10:08:57'
+ #AND MonitorId IN (5,6,20,15,33,37,38,41);
+ $select .= ' E.Id, E.MonitorId, E.StartDateTime, E.EndDateTime, E.Cause, E.Length, E.Frames, E.Width, E.Archived';
+ $select .= '
+ ,UNIX_TIMESTAMP(E.StartDateTime) AS StartTimeSecs,
+ CASE WHEN E.EndDateTime IS NULL THEN (SELECT NOW()) ELSE E.EndDateTime END AS EndDateTime,
+ CASE WHEN E.EndDateTime IS NULL THEN (SELECT UNIX_TIMESTAMP(NOW())) ELSE UNIX_TIMESTAMP(E.EndDateTime) END AS EndTimeSecs
+ ';
+ $where .= " AND (";
+ //$where .= " (E.EndDateTime >='".$startDateTime."'";
+ //$where .= " AND E.StartDateTime <='".$endDateTime."')";
+ //The last event that has not yet ended.
+ $join .= "
+ INNER JOIN (
+ SELECT
+ MonitorId,
+ MAX(Id) AS LastEvent
+ FROM
+ Events
+ GROUP BY
+ MonitorId) AS top ON (E.MonitorId = top.MonitorId)
+ ";
+ if ($startDateTime == $endDateTime) {
+ // We receive an event that should be played at a specific moment.
+ $where .= "
+ (E.Id = top.LastEvent AND E.EndDateTime IS NULL AND E.StartDateTime <='".$endDateTime."')
+ OR
+ (E.EndDateTime >='".$startDateTime."' AND E.StartDateTime <='".$endDateTime."')
+ ";
+ // Получаем ближайшую дату к дате по которой кликнули, т.к. могут быть сбойные события, которые не имеют даты окончания и могут "перекрывать" остальные события.
+ $order .= ' ORDER BY E.StartDateTime DESC';
+ } else {
+ $where .= "
+ (E.Id = top.LastEvent AND E.EndDateTime IS NULL AND E.StartDateTime BETWEEN '".$startDateTime."' AND '".$endDateTime."')
+ OR
+ (E.EndDateTime >='".$startDateTime."' AND E.StartDateTime <='".$endDateTime."')
+ ";
+ $order .= ' ORDER BY E.StartDateTime ASC, E.MonitorId ASC';
+ }
+ $where .= ")";
+ } else if ($actionRange == 'first') {
+ //Temporarily not used
+ if ($oneMonitors) {
+ } else {
+ }
+ } else if ($actionRange == 'prev') {
+ if ($oneMonitors) {
+ #SELECT *
+ #FROM Events
+ #WHERE StartDateTime<'2024-06-20 11:08:57'
+ #AND MonitorId=5
+ #ORDER BY StartDateTime DESC
+ #LIMIT 1;
+ $select .= ' *';
+ $where .= " AND E.StartDateTime <='".$startDateTime."'";
+ $order .= ' ORDER BY E.StartDateTime DESC';
+ $limit .= ' LIMIT 1';
+ } else {
+ #SELECT MonitorId, StartDateTime, MAX(Id) AS eventId
+ #FROM Events
+ #WHERE ((StartDateTime<'2024-06-20 11:08:57')
+ #AND (MonitorId IN (5,6,20,15,33,37,38,41)))
+ #GROUP BY MonitorId
+ // StartDateTime в ответе будет НЕ правильный!
+ $select .= ' E.MonitorId, ANY_VALUE(E.StartDateTime), MAX(E.Id) AS eventId';
+ $where .= " AND E.StartDateTime <='".$startDateTime."'";
+ $group .= ' GROUP BY E.MonitorId';
+ }
+ } else if ($actionRange == 'last') {
+ #SELECT MonitorId, StartDateTime, MAX(Id) AS eventId
+ #FROM Events
+ #WHERE MonitorId IN (5,6,20,15,33,37,38,41)
+ #GROUP BY MonitorId
+ // StartDateTime в ответе будет НЕ правильный! Но мы его и не будем включать в выборку.
+ // Нам достаточно MAX(E.Id) - это и будет последнее соьытие.
+# $select .= ' E.MonitorId, ANY_VALUE(E.StartDateTime), MAX(E.Id) AS eventId';
+ $select .= ' E.MonitorId, MAX(E.Id) AS eventId';
+ #$where .= " AND E.EndDateTime IS NOT NULL"; //Если событие еще не окончено, то из него почему-то не возможно получить "Frame"
+ $group .= ' GROUP BY E.MonitorId';
+ } else if ($actionRange == 'next') {
+ if ($oneMonitors) {
+ #SELECT *
+ #FROM Events
+ #WHERE StartDateTime>'2024-06-20 11:08:57'
+ #AND MonitorId=5
+ #ORDER BY StartDateTime ASC
+ #LIMIT 1;
+ $select .= ' *';
+ $where .= " AND E.StartDateTime >='".$startDateTime."'";
+ $order .= ' ORDER BY E.StartDateTime ASC';
+ $limit .= ' LIMIT 1';
+ } else {
+ #SELECT MonitorId, StartDateTime, MIN(Id) AS eventId
+ #FROM Events
+ #WHERE ((StartDateTime>'2024-06-20 11:08:57')
+ #AND (MonitorId IN (5,6,20,15,33,37,38,41)))
+ #GROUP BY MonitorId
+ // StartDateTime в ответе будет НЕ правильный!
+ $select .= ' E.MonitorId, ANY_VALUE(E.StartDateTime), MIN(E.Id) AS eventId';
+ $where .= " AND E.StartDateTime >='".$startDateTime."'";
+ $group .= ' GROUP BY E.MonitorId';
+ }
+ } else if ($actionRange == 'minData') {
+ #SELECT MonitorId, MIN(StartDateTime) AS minData
+ #FROM Events
+ #WHERE MonitorId IN (5,6,20,15,33,37,38,41)
+ $select .= ' ANY_VALUE(E.MonitorId), MIN(E.StartDateTime) AS minData';
+ }
+
+ /* IMPORTANT! Here we process the filter used by IgorA100 instead of the main one. */
+ if (is_array($filter)) {
+ foreach ($filter as $name => $value) {
+ if ($name == 'Archived') {
+ if (isset($value) && $value !== '') {
+ $where .= ' AND E.Archived ='.$value;
+ }
+ } else if ($name == 'Notes') {
+ $whereArr = '';
+ if (isset($value) && $value !== '') {
+ if (is_array($value)) {
+ $whereArr .= ' AND (';
+ foreach($value as $val){
+ $whereArr .= ' E.Notes LIKE \'%'.$val.'%\' OR';
+ }
+ } else {
+ $where .= ' AND E.Notes LIKE \'%'.$val.'%\'';
+ }
+ }
+ if ($whereArr) $whereArr = rtrim($whereArr, 'OR') . ')';
+ $where .= $whereArr;
+ } else if ($name == 'Tags') {
+ if (isset($value) && $value !== '') {
+ $select .= ' ,ET.EventId, ET.TagId';
+ $where .= ' AND ET.EventId IS NOT NULL';
+ if (is_array($value)) {
+ $where .= ' AND ET.TagId IN ('.implode(",",$value).')';
+ } else {
+ $where .= ' AND ET.TagId="'.$value.'"';
+ }
+ $join .= '
+ LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
+ LEFT JOIN Tags AS T ON T.Id = ET.TagId
+ ';
+ $group .= ($group) ? ', ET.EventId' : ' GROUP BY ET.EventId';
+ }
+ }
+ }
+ }
+
+ $sql = $select . $from . $join . $where . $order . $group . $limit;
+
+ if ($fullResponse) {
+ //A long, but forced and rare query that returns all columns of the table
+ $sql = '
+ SELECT E.Id, E.MonitorId, E.Width, E.Height, E.Length, E.Frames, E.Archived, E.Cause, E.StartDateTime,
+ UNIX_TIMESTAMP(E.StartDateTime) AS StartTimeSecs,
+ CASE WHEN E.EndDateTime IS NULL THEN (SELECT NOW()) ELSE E.EndDateTime END AS EndDateTime,
+ CASE WHEN E.EndDateTime IS NULL THEN (SELECT UNIX_TIMESTAMP(NOW())) ELSE UNIX_TIMESTAMP(E.EndDateTime) END AS EndTimeSecs
+# '.$from.' AS p1
+ '.$from.'
+ INNER JOIN
+ ('
+ . $sql .
+ ') AS p2
+ ON E.MonitorId = p2.MonitorId
+ AND E.Id = p2.eventId';
+ $sql .= ' GROUP BY E.Id, E.MonitorId, E.Width, E.Height, E.Length, E.Frames, E.Archived, E.Cause, E.StartDateTime';
+ }
+ return $sql;
+ }
+
+ public static function findDataInEventsArray($monitorId, $eventsArray) {
+ $item = null;
+ foreach($eventsArray as $event) {
+ if ($monitorId == $event['MonitorId']) {
+ $item = $event;
+ break;
+ }
+ }
+ return $item;
+ }
+}
+?>
diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js
index 884b94d4d1..b63dadddab 100644
--- a/web/skins/classic/views/js/montage.js
+++ b/web/skins/classic/views/js/montage.js
@@ -1,5 +1,7 @@
"use strict";
-const monitors = new Array();
+var monitors = new Array();
+var monitorsId = new Array();
+var arrRatioMonitors = [];
var monitors_ul = null;
var idle = 0;
@@ -7,15 +9,18 @@ const VIEWING = 0;
const EDITING = 1;
var mode = 0; // start up in viewing mode
-
+var observer;
var objGridStack;
var layoutColumns = 48; //Maximum number of columns (items per row) for GridStack
var changedMonitors = []; //Monitor IDs that were changed in the DOM
+var onvisibilitychangeTriggered = false;
var scrollBbarExists = null;
var movableMonitorData = []; //Monitor data (id, width, stop (true - stop moving))
var TimerHideShow = null;
+var monitorCanvasCtx = [];
+var monitorCanvasObj = [];
const presetRatio = new Map([
['auto', ''],
@@ -42,9 +47,46 @@ const presetRatio = new Map([
]);
var defaultPresetRatio = 'auto';
-
var averageMonitorsRatio;
+var montageMode = ''; //Live || inRecording
+var prevMontageMode = ''; //The previous mode from which we switch
+var eventsPlay = false;
+
+var timeline;
+var idTimelineCustomTimeMarker = 'id';
+var customTimeSpecified = false;
+const timelineBlock = document.getElementById('timelinediv');
+const wrapperTimelineBlock = document.getElementById('wrapper-timeline');
+var createdTimelineExtraInfo; //Let's create a block in the Timeline table to use the empty space to good use.
+var timelineCurrentTimeHTML;
+var timelineExtraInfo;
+var intervalRefreshCheckNextEvent;
+var intervalRefreshUpdateCurrentTime;
+var intervalSynchronizeEventsWithTimeline;
+var eventsOnTimeline = []; //Events displayed on Timeline
+var prevDateTimeTimelineInMilliSec = null; //Required for fast or slow motion playback
+
+var eventsTable = []; //Храним текущее (если идет воспроизвдение) или следующее (если в данный момент нет воспроизведения) событие в т.ч. и для рассчета Scale.
+var getEventsInProgress;
+var prevRangeWindowTimeline = {}; //Диапазон видимых значений до изменения масштаба Timeline.
+const alertLoadEvents = $j("#alert-load-events");
+
+var selectStartDateTime = document.getElementById("StartDateTime");
+var selectEndDateTime = document.getElementById("EndDateTime");
+var selectArchived = document.getElementById("filterArchived");
+var selectTags = document.getElementById("filterTags");
+var selectNotes = document.getElementById("filterNotes");
+
+var startDateFirstEvent = dateTimeToISOLocal(new Date(1900, 1, 1)); //Дата начала первого события группы мониторов для TimeLine, далее будет переопределена
+
+var shifted = null;
+var ctrled = null;
+var alted = null;
+
+var lastSpeed; //Странно, но этого вообще не было и похоже это не было реализовано
+var streamCmdTimer = null;
+
function isPresetLayout(name) {
return ((ZM_PRESET_LAYOUT_NAMES.indexOf(name) != -1) ? true : false);
}
@@ -53,22 +95,39 @@ function getCurrentNameLayout() {
return layouts[parseInt($j('#zmMontageLayout').val())].Name;
}
+function showSpeed(val) {
+ // updates slider only
+ $j('#speedslideroutput').text(parseFloat(speeds[val]).toFixed(2).toString() + " x");
+}
+
function setSpeed(newSpeed) {
- lastSpeed = currentSpeed;
- currentSpeed = newSpeed;
- setCookie('speed', String(currentSpeed), 3600);
- for (let i=0, length = monitors.length; i < length; i++) {
- const monitorStream = monitors[i];
- if (lastSpeed != '0' && currentSpeed != '0') {
- monitorStream.setMaxFPS(currentSpeed);
- } else if (lastSpeed != '0') {
- monitorStream.pause();
- // pause
- } else {
- // play
- monitorStream.play();
+ if (montageMode == 'Live') {
+ // IMPORTANT The code is left from the old montage.js, but it didn't seem to work for Live mode!
+ lastSpeed = currentSpeed;
+ currentSpeed = newSpeed;
+ setCookie('speedForLive', String(currentSpeed), 3600);
+ for (let i=0, length = monitors.length; i < length; i++) {
+ const monitorStream = monitors[i];
+ if (lastSpeed != '0' && currentSpeed != '0') {
+ monitorStream.setMaxFPS(currentSpeed);
+ } else if (lastSpeed != '0') {
+ // pause
+ monitorStream.pause();
+ } else {
+ // play
+ monitorStream.play();
+ }
+ this.started = true;
}
- this.started = true;
+ } else { //inRecording
+ ////console.log("+++newSpeed", newSpeed);
+ speedIndex = newSpeed;
+ currentSpeed = parseFloat(speeds[speedIndex]);
+ setCookie('speed', String(currentSpeed), 3600);
+ //playSecsPerInterval = Math.floor( 1000 * currentSpeed * currentDisplayInterval ) / 1000000;
+ showSpeed(speedIndex);
+ //timerFire();
+ setSpeedForMonitors(currentSpeed * 100);
}
}
@@ -77,29 +136,83 @@ function speedChange(ddm) {
if (lastSpeed == '0') {
pausedClicked();
} else {
- playClicked();
+ clickedPlay();
}
}
-function pauseClicked() {
- console.log('pauseClicked');
- setSpeed('0');
- $j('#playBtn').show();
- $j('#pauseBtn').hide();
- $j('#speed').val(speed);
+function clickedStop() {
+ console.log('clickedStop');
+ if (montageMode == 'Live') {
+
+ } else { //inRecording
+ stopAllEvents();
+ }
}
-function playClicked() {
- console.log(lastSpeed);
- if (!lastSpeed) lastSpeed = 'auto';
- setSpeed(lastSpeed);
- $j('#playBtn').hide();
- $j('#pauseBtn').show();
- $j('#speed').val(speed);
+function clickedPause() {
+ console.log('clickedPause');
+ if (montageMode == 'Live') {
+ setSpeed('0');
+ $j('#playBtn').show();
+ $j('#pauseBtn').hide();
+ $j('#speed').val(speed);
+ } else { //inRecording
+ pauseAllEvents();
+ }
}
-function getStream(id) {
- return document.getElementById('liveStream'+id);
+function clickedPlay() {
+ console.log("lastSpeed==>", lastSpeed);
+ if (montageMode == 'Live') {
+ if (!lastSpeed) lastSpeed = 'auto';
+ setSpeed(lastSpeed);
+ $j('#playBtn').hide();
+ $j('#pauseBtn').show();
+ $j('#speed').val(speed);
+ } else { //inRecording
+ startAllEvents();
+ }
+}
+
+function streamPrev() {
+
+}
+
+function streamFastRev() {
+
+}
+
+function streamSlowRev() {
+
+}
+
+function streamSlowFwd() {
+ for (var monitorId in eventsTable) {
+ eventInfo = getEventInfoFromEventsTable({what: 'current', mid: monitorId});
+ if (eventInfo.status == 'started' ) {
+ const url = new URL(eventInfo.src);
+ const connkey = url.searchParams.get('connkey');
+ if (!connkey) continue;
+ const monitor = monitors.find((o) => {
+ return parseInt(o["id"]) === parseInt(monitorId);
+ });
+ streamReq({
+ command: CMD_SLOWFWD,
+ monitorId: monitorId,
+ connkey: connkey,
+ eventId: eventInfo.eventId,
+ monitorUrl: monitor.url
+ });
+ }
+ }
+}
+
+function streamFastFwd() {
+
+}
+
+function streamNext() {
+
}
/**
@@ -206,40 +319,23 @@ function selectLayout(new_layout_id) {
setCookie('zmMontageLayout', layout_id);
} // end function selectLayout(element)
+/*
+* objInput: object
+*/
+function setInputed(objInput, value) {
+ let option;
-function changeHeight() { //Not used
-/* var height = $j('#height').val();
- setCookie('zmMontageHeight', height);
- for (var i = 0, length = monitors.length; i < length; i++) {
- const monitor = monitors[i];
- const monitor_frame = $j('#monitor'+monitor.id + " .monitorStream");
- monitor_frame.css('height', height);
- }
-*/}
-
-/**
- * called when the widthControl select element is changed
- */
-
-function changeWidth() { //Not used
-/* const width = $j('#width').val();
- const height = $j('#height').val();
-
- selectLayout(freeform_layout_id);
- $j('#width').val(width);
- $j('#height').val(height);
-
- for (let i = 0, length = monitors.length; i < length; i++) {
- monitors[i].setScale('0', width, height, false);
+ for (var i=0; i
*/
+function getSelectedMultiple(objSel) {
+ var result = [];
+ const options = objSel && objSel.options;
+ var opt;
+
+ for (var i=0, iLen=options.length; i
+* value: String or array. Object is not allowed.
+*/
function setSelected(objSel, value) {
+ if (!value) return;
+ var newValue =[];
+ if (typeof value === 'string') {
+ newValue.push(value);
+ } else {
+ newValue = value;
+ }
+
for (let i=0; i= 0) {
- id = stringToNumber(obj.getAttribute('id'));
- zmPanZoom.click(id);
+ if (thisClickOnStreamObject(obj)) {
+ monitorId = stringToNumber(obj.getAttribute('id'));
+ zmPanZoom.click(monitorId);
}
}
function startMonitors() {
for (let i = 0, length = monitors.length; i < length; i++) {
const monitor = monitors[i];
+ if (monitor.capturing == 'None') continue;
// Why are we scaling here instead of in monitorstream?
const obj = document.getElementById('liveStream'+monitor.id);
- if (obj.src) {
- const url = new URL(obj.src);
- let scale = parseInt(obj.clientWidth / monitor.width * 100);
- if (scale > 100) scale = 100;
- url.searchParams.set('scale', scale);
- obj.src = url;
+ if (obj) {
+ if (obj.src) {
+ const url = new URL(obj.src);
+ let scale = parseInt(obj.clientWidth / monitor.width * 100);
+ if (scale > 100) scale = 100;
+ url.searchParams.set('scale', scale);
+ obj.src = url;
+ }
+ } else {
+ console.log(`startMonitors NOT FOUND ${'liveStream'+monitor.id}`);
}
const isOut = isOutOfViewport(monitor.getElement());
@@ -588,15 +729,16 @@ function startMonitors() {
if ((monitor.type == 'WebSite') && (monitor.refresh > 0)) {
setInterval(reloadWebSite, monitor.refresh*1000, i);
}
- monitor.setup_onclick(handleClick);
}
}
-function stopMonitors() { //Not working yet.
+function stopAllMonitors() {
for (let i = 0, length = monitors.length; i < length; i++) {
+ if (typeof(monitors[i]) === 'undefined') continue;
+ if (!monitorDisplayedOnPage(monitors[i].id)) continue;
//monitors[i].stop();
- //monitors[i].kill();
- monitors[i].streamCommand(CMD_QUIT);
+ monitors[i].kill();
+ //monitors[i].streamCommand(CMD_QUIT);
}
monitors.length = 0;
}
@@ -637,6 +779,8 @@ function windowResize() { //Only used when trying to apply "changeScale". It wil
}
function buildRatioSelect(objSelect) {
+ if (typeof(objSelect) === 'undefined') return;
+ objSelect.options.length = 0;
presetRatio.forEach(function(value, key) {
if (key == "auto") {
objSelect.options[objSelect.options.length] = new Option("Auto", key);
@@ -686,12 +830,175 @@ function calculateAverageMonitorsRatio(arrRatioMonitors) {
}
function initPage() {
+ if (getCookie('zmMontageMode') == 'inRecording') {
+ setInRecordingMode();
+ } else {
+ setLiveMode();
+ }
+
+ // +++ For MontageReview
+ $j('#scaleslider').bind('change', function() {
+ setScale(this.value);
+ });
+ $j('#scaleslider').bind('input', function() {
+ showScale(this.value);
+ });
+ $j('#speedslider').bind('change', function() {
+ setSpeed(this.value);
+ });
+ $j('#speedslider').bind('input', function() {
+ showSpeed(this.value);
+ });
+
+ $j('#liveButton').bind('click', function() {
+ setLive(1-liveMode);
+ });
+ $j('#fit').bind('click', function() {
+ setFit(1-fitMode);
+ });
+ $j('#archive_status').bind('change', function() {
+ this.form.submit();
+ });
+ $j('#fieldsTable input, #fieldsTable select').each(function(index) {
+ const el = $j(this);
+ if (el.hasClass('datetimepicker')) {
+ //el.datetimepicker({timeFormat: "HH:mm:ss", dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false, onClose: changeDateTime, todayHighlight: false,});
+ el.datetimepicker({timeFormat: "HH:mm:ss", dateFormat: "yy-mm-dd", maxDate: new Date(), constrainInput: false, onClose: changeDateTime, todayHighlight: false,});
+ } else if (el.hasClass('datepicker')) {
+ //el.datepicker({dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false, onClose: changeDateTime});
+ el.datepicker({dateFormat: "yy-mm-dd", maxDate: new Date(), constrainInput: false, onClose: changeDateTime});
+ } else {
+
+ }
+ });
+
+ $j('#StartDateTime, #EndDateTime').click( handlerClickDateTime );
+ //$j('#filterArchived, #filterTags, #filterNotes').click( handlerClickOtherFilters );
+ $j('#filterArchived, #filterTags, #filterNotes').change( handlerClickOtherFilters );
+
+ // --- For MontageReview
+
+ // Creating a ResizeObserver Instance
+ observer = new ResizeObserver((objResizes) => {
+ const blockContent = document.getElementById('content');
+ const currentScrollBbarExists = blockContent.scrollHeight > blockContent.clientHeight;
+ if (scrollBbarExists === null) {
+ scrollBbarExists = currentScrollBbarExists;
+ }
+ if (currentScrollBbarExists != scrollBbarExists) {
+ scrollBbarExists = currentScrollBbarExists;
+ return;
+ }
+ objResizes.forEach((obj) => {
+ //const id = stringToNumber(obj.target.id);
+ //if (mode != EDITING && !changedMonitors.includes(id)) {
+ if (mode != EDITING) {
+ setTriggerChangedMonitors(stringToNumber(obj.target.id));
+ //changedMonitors.push(id);
+ }
+ });
+ });
+
+ $j(document).on('keyup keydown', function(e) {
+ shifted = e.shiftKey ? e.shiftKey : e.shift;
+ ctrled = e.ctrlKey;
+ alted = e.altKey;
+ });
+
+ document.onvisibilitychange = () => {
+ if (document.visibilityState === "hidden") {
+ TimerHideShow = clearTimeout(TimerHideShow);
+ TimerHideShow = setTimeout(function() {
+
+ //if (onvisibilitychangeTriggered) return;
+ //onvisibilitychangeTriggered = true;
+ //Stop monitors when closing or hiding page
+ if (montageMode == 'Live') {
+ for (let i = 0, length = monitors.length; i < length; i++) {
+ //monitors[i].streamCmdTimer = clearInterval(monitors[i].streamCmdTimer);
+ if (!monitorDisplayedOnPage(monitors[i].id)) continue;
+//console.log("*********monitors[i].kill() " + monitors[i].id + " in LIVE mode");
+ monitors[i].kill();
+ }
+ } else { //inRecording
+ if (eventsPlay) {
+ stopAllEvents();
+ eventsPlay = true;
+ }
+ }
+ }, 15*1000);
+ } else {
+ TimerHideShow = clearTimeout(TimerHideShow);
+ //if (!onvisibilitychangeTriggered) return;
+ //onvisibilitychangeTriggered = false;
+ //Start monitors when show page
+ if (montageMode == 'Live') {
+ for (let i = 0, length = monitors.length; i < length; i++) {
+ const monitor = monitors[i];
+ const isOut = isOutOfViewport(monitor.getElement());
+ if ((!isOut.all) && !monitor.started && monitorDisplayedOnPage(monitor.id)) {
+ console.log("*********monitor.start() " + i + " in LIVE mode");
+ monitor.start();
+ }
+ }
+ } else { //inRecording
+ if(eventsPlay) {
+console.log("*********startAllEvents in RECORDING mode");
+ startAllEvents();
+ }
+ }
+ }
+ };
+ document.getElementById('timelinediv').onclick = function (event) {
+ var props = timeline.getEventProperties(event)
+ console.log("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", props);
+ }
+
+ setInterval(() => { //Updating GridStack resizeToContent, Scale & Ratio
+ //return;
+ if (changedMonitors.length > 0) {
+//console.log("changedMonitors.length", changedMonitors.length);
+ changedMonitors.slice().reverse().forEach(function(item, index, object) {
+/*В*///console.log("changedMonitors.item", item);
+ //const img = document.getElementById('liveStream'+item);
+ //const img = (document.getElementById('liveStream'+item)) ? document.getElementById('liveStream'+item) : document.getElementById('evtStream'+item);
+ const img = getStream(item);
+ if (!img) { //IgorA100 ВРЕМЕННО для просмотре в записи
+ changedMonitors.splice(object.length - 1 - index, 1);
+ return;
+ }
+//console.log("changedMonitors.item", item, "", img.offsetHeight);
+ //if (1 == 1) { //А может надо типа так, т.е. безусловно....
+ if (img.offsetHeight > 20 && objGridStack) { //Required for initial page loading
+ //if (img.complete) { //Required for initial page loading ВАЖНО! Попробуем так... Не работает для тега