var isLoggedIn = false; function CamConfiguration (camId, appendTo = $("#div#container")) { var _camId, _parent, _div, _data; } /* CamConsole.php Okay, so this class is a big one! Builds a camera console that displays most recent live view, a map with viewshed polygon, & panorama If user is logged in, will offer additional tabs - Historical View: view cam history, including both single frame and panoramas. Offers azimuth selection on single frame searches - Configuration: alter camera configuration, including location info, connection settings, panorama configuration, and manual control */ function CamConsole(camId, appendTo=$("div#container")) { var _camId, _data, _div, _output, _controlBar, _panorama, _camMonitor, _history, _camControl, _t; this.build = function () { /* So, build the cam console, including the additional tabs if user is logged in. Default to Latest View tab. If this is a new camera being created, switch to config tab. */ var me = this; var navigation = $("
").addClass("btn-group cam-menu").attr({ "role":"group", "aria-label":"Flir Navigation" }); me._controlBar = $("
").addClass('control-bar').append($("
").addClass("cam-name-heading-box").append($("

").addClass('cam-name-heading')), navigation); var panoDiv = $("
").addClass('panorama-area'); var camMonitorDiv = $("
").addClass('cam-monitor-area'); me._div = $("
").addClass("camera-console"); me._div.append(me._controlBar); me._camMonitor = new CamMonitor(me._camId, me._div); me._panorama = new CamPanorama(me._camId, me._div, me._camMonitor._map); me._output.append(me._div); } this.getData = function() { /* Get the camera's information (to title the page) */ var me = this; var success = function (result) { if (result.code == 1) { me._data = result.data; } me.updateHeading(); } var finish = function () { } var fail = function() { } if (!me._paused && !isNaN(me._camId)) { var query = new AjaxQuery("/api/getCameraInfo", "GET", {camId: me._camId}, success, finish, fail); } me._t = setTimeout(function() { me.getData(); }, 30000) } this.init = function () { var me = this; me._camId = camId; me._output = appendTo; me.getData(); me.build(); } this.updateHeading = function () { /* Update the heading to show the camera's name */ var me = this; var headingBox = me._div.find('.cam-name-heading-box'); var heading = me._div.find('.cam-name-heading'); headingBox.html(heading); heading.html(" > " + me._data.camname.replaceAll("_", " ")); heading.prepend($("").attr({"href":"/cameras"}).html("Camera Management")); if (me._data.camnodeid) { let nodeButton = $("").addClass('btn btn-secondary node-button').attr({'href':'/node/1/'+me._data.camnodeid}).html( $("").attr({'src':'img/button.svg'}).css({"width":"24px"}) ); headingBox.append( nodeButton /*''+me._data.camnodeid+''*/ ); } } this.init(); } function CamGallery (camType = "active", div = null, data = null, refreshRate = 30000, exclude = Array(), config = false) { /* Display a grid of auto-updating camera thumbnails, for either active or inactive cams */ var _camType, _div, _thumbnails, _data, _refreshRate, _exclude, _overrideMobileHide; this.buildThumbnails = function() { /* Build the thumbnail grid */ var me = this; if (screen.width > 1000 || me._overrideMobileHide) { me._data.forEach((val) => { me._thumbnails[val.camId] = new CamThumbnail(val.camId, me._div, val, Boolean(config)); if (me._exclude.indexOf(val.camId) > -1) { me._thumbnails[val.camId]._div.hide(); } if (me._config) { me._thumbnails[val.camId]._div.on("click", function () { me._config.switchGalleries(val.camId, me._camType); }) } }); } } this.destroy = function (camId) { var me = this; if (Array.isArray(me._camType)) { me._thumbnails[camId]._div.remove(); me._camType.splice(me._camType.indexOf(camId), 1); } else if (me._camType == 'active') { me._thumbnails[val.camId]._div.hide(); } } this.getData = function () { /* Retrieve data for the cameras */ var me = this; var success = function(result) { if (result.code == 1) { me.updateData(Object.values(result.data)); } me._t = setTimeout(() => { me.getData(); }, me._refreshRate); } var finish = function () {} var fail = function () {} var query = new AjaxQuery("/api/getCameraData", "GET", {cameraIds: me._camType}, success, finish, fail); } this.rebuildThumbnails = function() { /* Delete all the thumbnails and call the build function again (this is in case cameras are added, removed, or deactivated while a user has this page active) */ var me = this; me._thumbnails.forEach((thumbnail) => { thumbnail._div.remove(); }); me.buildThumbnails(); } this.updateData = function(data) { var me = this; me._data = data; me._data.sort(function (a, b) { let aName = a.camName.toLowerCase(), bName = b.camName.toLowerCase(); if (aName < bName) { return -1; } if (aName > bName) { return 1; } return 0; }); if (me._thumbnails.length == 0) { me.buildThumbnails(); } else { me.updateThumbnails(); } } this.updateThumbnails = function() { /* Update the image shown by each thumbnail with the new data. */ var me = this; me._data.every((val) => { if (me._thumbnails[val.camId]) { me._thumbnails[val.camId].updateData(val); } else { me.rebuildThumbnails(); return false; } return true; }); me._thumbnails.every((thumbnail, camId) => { if (camId != 0 && !me._data.some(function(d) { return d.camId == camId})) { me.rebuildThumbnails(); return false; } else { return true; } }) if (Array.isArray(me._camType) && me._config && me._config._shown) { me._config._map.updateData(me._data); } } this.init = function () { var me = this; me._camType = camType; me._div = div ?? $("
").addClass("cam-gallery").attr({"id":me._camType+"-cams"}); me._refreshRate = refreshRate; me._thumbnails = Array(); me._exclude = exclude; me._config = config; me._overrideMobileHide = false; if (data == null) { me.getData(); } else { me.updateData(data); } } this.init(); } function CamHistory(camId, appendTo = $("div#container"), panorama = null, camMonitor = null) { var _camId, _parent, _div, _form, _overlay, _controls, _display, _panorama, _camMonitor, _data, _paused; /* Arguably the most complicated JS class I built for this site. Lots of event listeners and case checks from play/pause, prev/next, and seeker-scroll Basic function of this class is to allow the user to search for camera data with parameters. Major params are: Still/Timelapse - Still gets closest still to given timestamp, timelapse returns all data between two timestamps Panorama/Single Frame - Panorama returns panorama data (patchwork for still, stitched for timelapse), single frame returns single camera frames and viewshed polygon info Date/Time - For a still, select a date/time, will return closest data to that timestamp (Range) For a timelapse, select a start & end time, will return all available data of the selected type between those times Azimuth - Limits query to within 1/3 a view frame of defined azimuth (Range) Limits query to between 2 azimuths with 1/3 a view frame padding on either side. */ this.build = function() { /* Build the form for requesting cam history */ var me = this; me._div = $("
").addClass("cam-history"); me._form = $("
").addClass("history-form").append( $("
").addClass("form-group row").append( $("").addClass("col-form-label float-sm-left col-sm-2 pt-0").html("History Mode"), $("
").addClass("col-sm-10").append( $("
").addClass("form-check form-check-inline").append( $("").addClass("form-check-input").attr({ "type":"radio", "required":true, "name":"historyType", "id":"cam-"+me._camId+"-still-radio", "value":"still" }).on("change", function (e) {me.toggleDateTimeShown(e.target)}), $("
").addClass("form-check form-check-inline").append( $("").addClass("form-check-input").attr({ "type":"radio", "required":true, "name":"historyType", "id":"cam-"+me._camId+"-timelapse-radio", "value":"timelapse" }).on("change", function (e) {me.toggleDateTimeShown(e.target)}), $("
").addClass("form-group row").append( $("").addClass("col-form-label float-sm-left col-sm-2 pt-0").html("Image Type"), $("
").addClass("col-sm-10").append( $("
").addClass("form-check form-check-inline").append( $("").addClass("form-check-input").attr({ "type":"radio", "required":true, "name":"imageType", "id":"cam-"+me._camId+"-pano-radio", "value":"pano" }).on("change", function (e) {me.toggleAzimuthShown(e.target)}), $("
").addClass("form-check form-check-inline").append( $("").addClass("form-check-input").attr({ "type":"radio", "required":true, "name":"imageType", "id":"cam-"+me._camId+"-frame-radio", "value":"frame" }).on("change", function (e) {me.toggleAzimuthShown(e.target)}), $("
").addClass("form-group row").css({"display":"none"}).append( $("
").addClass("col-sm-10").append( $("").addClass("form-control").attr({ "type":"text", "name":"datetime", "id":"cam-"+me._camId+"-datetime-input" }).daterangepicker({ "autoApply": true, "autoUpdateInput":true, "singleDatePicker": true, "maxDate": moment().tz("America/Los_Angeles"), "timePicker": true, "locale": { "format": "L LT z", "monthNames": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] }, "startDate": moment().tz("America/Los_Angeles"), "endDate": moment().tz("America/Los_Angeles") }) ) ), $("
").addClass("form-group row").css({"display":"none"}).append( $("
").addClass("col-sm-10").append( $("").addClass("form-control").attr({ "type":"text", "name":"datetime-range", "id":"cam-"+me._camId+"-datetime-range-input" }).daterangepicker({ "autoApply": true, "autoUpdateInput":true, "maxDate": moment().tz("America/Los_Angeles"), "timePicker": true, "locale": { "format": "L LT z", "monthNames": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ] }, "startDate": moment().tz("America/Los_Angeles"), "endDate": moment().tz("America/Los_Angeles") }) ) ), $("
").addClass("form-group row").css({"display":"none"}).append( $("
").addClass("col-sm-10").append( $("").addClass("circle-range-select").attr({ "name":"azimuth", "data-auto-init":true, "data-single-value":true, "data-min":-1, "data-max":359, "value":"-1", "id":"cam-"+me._camId+"-azimuth-select" }) ) ), $("
").addClass("form-group row").css({"display":"none"}).append( $("
").addClass("col-sm-10").append( $("").addClass("circle-range-select").attr({ "name":"azimuth-range", "data-auto-init":true, "data-min":-1, "data-max":359, "value":"-1;-1", "id":"cam-"+me._camId+"-azimuth-range-select" }) ) ), $("
").addClass("form-group row").append( $("
").addClass("col-sm-10").append( $("