diff --git a/definitions/base.js b/definitions/base.js index 630b6a83..b1f70490 100644 --- a/definitions/base.js +++ b/definitions/base.js @@ -140,7 +140,34 @@ module.exports = function(s,config,lang){ "value": "1" } ] - } + }, + { + "name": "detail=geolocation", + "field": lang["Geolocation"], + "example": "49.2578298,-123.2634732", + "description": lang["fieldTextGeolocation"], + }, + { + "id": "monitor-settings-monitor-map-container", + "style": "position: relative", + "fieldType": "div", + divContent: ` +
+ +
+ +
+ +
+ +
+ +
+ +
+
+
`, + }, ] }, "Connection": { @@ -7718,7 +7745,7 @@ module.exports = function(s,config,lang){ ] }, { - icon: 'map-marker', + icon: 'barcode', label: `${lang['Power Viewer']}`, pageOpen: 'powerVideo', }, @@ -7741,6 +7768,11 @@ module.exports = function(s,config,lang){ }, ] }, + { + icon: 'map-marker', + label: `${lang['Monitor Map']}`, + pageOpen: 'monitorMap', + }, { icon: 'file-o', label: `${lang['FileBin']}`, @@ -8877,146 +8909,130 @@ module.exports = function(s,config,lang){ "box-wrapper-class": "row", "info": [ { - title: "New to Shinobi?", + title: lang["New to Shinobi?"], info: `Try reading over some of these links to get yourself started.`, buttons: [ { icon: 'newspaper-o', color: 'default', - text: 'After Installation Guides', - href: 'https://shinobi.video/docs/configure', - class: '' + text: lang.afterInstallationGuides, + href: 'https://shinobi.video/docs/configure' }, { icon: 'plus', color: 'default', - text: 'Adding an H.264 Camera', - href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera', - class: '' + text: lang.addingAnH264Camera, + href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera' }, { icon: 'plus', color: 'default', - text: 'Adding an MJPEG Camera', - href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera', - class: '' + text: lang.addingAnMJPEGCamera, + href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera' }, { icon: 'gears', color: 'default', - text: 'RTSP Camera Optimization', - href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera', - class: '' + text: lang.rtspCameraOptimization, + href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera' }, { icon: 'comments-o', color: 'info', - text: 'Community Chat', - href: 'https://discord.gg/ehRd8Zz', - class: '' + text: lang.communityChat, + href: 'https://discord.gg/ehRd8Zz' }, { icon: 'reddit', color: 'info', - text: 'Forum on Reddit', - href: 'https://www.reddit.com/r/ShinobiCCTV', - class: '' + text: lang.forumOnReddit, + href: 'https://www.reddit.com/r/ShinobiCCTV' }, { icon: 'file-o', color: 'primary', - text: 'Documentation', - href: 'http://shinobi.video/docs', - class: '' + text: lang.Documentation, + href: 'http://shinobi.video/docs' } ] }, { bigIcon: "smile-o", - title: "It's a proven fact", - info: `Generosity makes you a happier person, please consider supporting the development.`, + title: lang.itsAProvenFact, + info: lang.generosityHappierPerson, buttons: [ { icon: 'share-square-o', color: 'default', - text: 'ShinobiShop Subscriptions', - href: 'https://licenses.shinobi.video/subscribe', - class: '' + text: lang['ShinobiShop Subscriptions'], + href: 'https://licenses.shinobi.video/subscribe' }, { icon: 'paypal', color: 'default', - text: 'Donate by PayPal', - href: 'https://www.paypal.me/ShinobiCCTV', - class: '' + text: lang['Donate by PayPal'], + href: 'https://www.paypal.me/ShinobiCCTV' }, { icon: 'bank', color: 'default', text: 'University of Zurich (UZH)', - href: 'https://www.zora.uzh.ch/id/eprint/139275/', - class: '' + href: 'https://www.zora.uzh.ch/id/eprint/139275/' }, ] }, { - title: "Shinobi Mobile", - info: `Your subscription key can unlock features for Shinobi Mobile running on iOS and Android today!`, + title: lang["Shinobi Mobile"], + info: lang.yourSubscriptionText, buttons: [ { icon: 'star', color: 'success', - text: 'Join Public Beta', - href: 'https://shinobi.video/mobile', - class: '' + text: lang['Get the Mobile App'], + href: 'https://shinobi.video/mobile' }, { icon: 'comments-o', color: 'primary', - text: '#mobile-client Chat', - href: 'https://discord.gg/ehRd8Zz', - class: '' + text: lang['#mobile-client Chat'], + href: 'https://discord.gg/ehRd8Zz' }, ] }, { - title: "Support the Development", - info: `Subscribe to any of the following to boost development! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! `, + title: lang.activateShinobi, + info: lang.howToActivate, buttons: [ { icon: 'share-square-o', color: 'default', text: 'Shinobi Mobile License ($5/m)', href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G31AZ9mknNCa6z', - class: '' }, { icon: 'share-square-o', color: 'default', text: 'Tiny Support Subscription ($10/m)', href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G42jNgIqXaWmIC', - class: '' }, { icon: 'share-square-o', color: 'default', - text: 'Shinobi Pro License ($75/m)', + text: '100 Camera License ($75/m)', href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G3LGdNwA8lSmQy', - class: '' }, ] }, { - title: "Donations, One-Time Boost", - info: `Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your PayPal Transaction ID can be used as a subscriptionId in your Shinobi configuration file.

Each 5 USD/EUR or 7 CAD will provide one month of activated usage. Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.`, + title: lang['Donations, One-Time Boost'], + info: lang.donationOneTimeText, width: 12, buttons: [ { icon: 'paypal', color: 'default', - text: 'Donate by PayPal', - href: 'https://www.paypal.me/ShinobiCCTV', - class: '' + text: lang['Donate by PayPal'], + href: 'https://www.paypal.me/ShinobiCCTV' }, ] }, @@ -9051,5 +9067,22 @@ module.exports = function(s,config,lang){ }, } }, + "Monitor Map": { + "section": "Monitor Map", + "blocks": { + "Search Settings": { + "name": lang["Monitor Map"], + "color": "blue", + "noHeader": true, + "noDefaultSectionClasses": true, + "info": [ + { + "fieldType": "div", + "id": "monitor-map-canvas", + } + ] + }, + } + }, }) } diff --git a/languages/en_CA.json b/languages/en_CA.json index 5f1b6966..67f1b6bb 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -14,6 +14,9 @@ "monitorDeleted": "Monitor Deleted", "accountCreationError": "Account Creation Error", "accountEditError": "Account Edit Error", + "Monitor Map": "Monitor Map", + "Geolocation": "Geolocation", + "fieldTextGeolocation": "The map coordinates of this camera in the real world. This will plot a point for your camera on the Monitor Map.", "Unmute": "Unmute", "byUser": "by user", "accountDeleted": "Account Deleted", @@ -371,9 +374,20 @@ "Execute Command": "Execute Command", "for Global Access": "for Global Access", "Help": "Help", + "Range": "Range", + "Direction": "Direction", + "Field of View": "Field of View", "Don't show this anymore": "Don't show this anymore", + "New to Shinobi?": "New to Shinobi?", + "Get the Mobile App": "Get the Mobile App", + "#mobile-client Chat": "#mobile-client Chat", + "Donations, One-Time Boost": "Donations, One-Time Boost", + "donationOneTimeText": "Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your PayPal Transaction ID can be used as a subscriptionId in your Shinobi configuration file.

Each 5 USD/EUR or 7 CAD will provide one month of activated usage. Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.", "Chat on Discord": "Chat on Discord", "Documentation": "Documentation", + "Donate by PayPal": "Donate by PayPal", + "Join Public Beta": "Join Public Beta", + "ShinobiShop Subscriptions": "ShinobiShop Subscriptions", "All Monitors": "All Monitors", "Motion Meter": "Motion Meter", "FFmpegTip": "FFprobe is a simple multimedia streams analyzer. You can use it to output all kinds of information about an input including duration, frame rate, frame size, etc.", @@ -990,6 +1004,7 @@ "WebdavErrorTextCreatingDir": "Cannot create directory.", "File Not Exist": "File Not Exist", "No Videos Found": "No Videos Found", + "activateRequiredLiveStream": "Live Stream is only available here with an Activated installation.", "FileNotExistText": "Cannot save non existant file. Something went wrong.", "CameraNotRecordingText": "Settings may be incompatible. Check encoders. Restarting...", "Camera is not running": "Camera is not running", @@ -1786,6 +1801,17 @@ "fieldTextChannelStreamScaleY": "Height of the stream image that is output after processing.", "fieldTextChannelStreamRotate": "Change the viewing angle of the video stream.", "fieldTextChannelSvf": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.", + "afterInstallationGuides": "After Installation Guides", + "addingAnH264Camera": "Adding an H.264 Camera", + "addingAnMJPEGCamera": "Adding an MJPEG Camera", + "rtspCameraOptimization": "RTSP Camera Optimization", + "communityChat": "Community Chat", + "forumOnReddit": "Forum on Reddit", + "forumOnReddit": "Forum on Reddit", + "activateShinobi": "Activate Shinobi", + "howToActivate": "Subscribe to any of the following to support the development and activate your Shinobi! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! ", + "Shinobi Mobile": "Shinobi Mobile", + "yourSubscriptionText": "Your subscription key can unlock features for Shinobi Mobile running on iOS and Android today!", "Last Updated": "Last Updated", "Z-Wave Manager": "Z-Wave Manager", "Z-Wave": "Z-Wave", @@ -1798,5 +1824,7 @@ "deleteFace": "Delete Face", "deleteFaceText": "Are you sure you want to delete ALL the images for this face? they will not be recoverable.", "deleteImage": "Delete Image", - "deleteImageText": "Are you sure you want to delete this image? it will not be recoverable." + "deleteImageText": "Are you sure you want to delete this image? it will not be recoverable.", + "itsAProvenFact": "It's a proven fact", + "generosityHappierPerson": "Generosity makes you a happier person, please consider supporting the development." } diff --git a/libs/webServer.js b/libs/webServer.js index 3f611fa7..609287bc 100644 --- a/libs/webServer.js +++ b/libs/webServer.js @@ -82,6 +82,7 @@ module.exports = function(s,config,lang,io){ 'home/fileBin', 'home/videosTable', 'home/studio', + 'home/monitorMap', 'confirm', 'home/help', ] diff --git a/web/assets/css/bs5.monitorMap.css b/web/assets/css/bs5.monitorMap.css new file mode 100644 index 00000000..9347547d --- /dev/null +++ b/web/assets/css/bs5.monitorMap.css @@ -0,0 +1,37 @@ +#monitor-map-canvas { + height: calc(100vh - 2rem); + border-radius: 20px; +} +#monitor-map-canvas .leaflet-map-pane { + transition: initial; +} +#monitor-map-canvas .leaflet-popup-content { + margin: 0px; + width: 400px!important; +} +#monitor-map-canvas .leaflet-popup-content-wrapper { + padding: 0px; + overflow: hidden; +} +#monitor-map-canvas iframe { + width: 100%; + height: 300px +} +#leaflet-monitor-block { + width:100%; +} +#leaflet-monitor-videos { + overflow:auto; + height: 250px; + padding: 0.4rem 0.5rem; +} +.leaflet-popup-content-wrapper, .leaflet-popup-tip { + background-color: #121417; +} +.leaflet-container a.btn { + color: #fff; +} + +.leaflet-marker-icon { + transition: 0s; +} diff --git a/web/assets/js/bs5.monitorMap.js b/web/assets/js/bs5.monitorMap.js new file mode 100644 index 00000000..81387146 --- /dev/null +++ b/web/assets/js/bs5.monitorMap.js @@ -0,0 +1,156 @@ +$(document).ready(function(){ + var theBlock = $('#tab-monitorMap') + var theMap = $('#monitor-map-canvas') + var loadedMap; + function loadPopupVideoList(monitor){ + var groupKey = monitor.ke + var monitorId = monitor.mid + getVideos({ + monitorId: monitorId, + limit: 10, + },function(data){ + var videos = data.videos + var html = '' + setTimeout(function(){ + var theVideoList = $(`#leaflet-monitor-videos`) + if(videos.length > 0){ + theVideoList.css('height','') + console.log(2,videos,videos.length) + $.each(videos,function(n,video){ + html += createVideoRow(video,`col-12 mb-2`) + }) + }else{ + theVideoList.css('height','initial') + html = `
${lang['No Videos Found']}
` + } + theVideoList.html(html) + },1000) + }) + } + function buildPinPopupHtml(monitor){ + var embedUrl = buildEmbedUrl(monitor) + var html = ` +
+
${userHasSubscribed ? `` : `
${lang.activateRequiredLiveStream}
`}
+
+
+
+
+ ` + return html + } + function getPinsFromMonitors(){ + var points = [] + var n = 0; + $.each(loadedMonitors,function(monitorId,monitor){ + var geolocation = monitor.details.geolocation + var modeIsOn = monitor.mode === 'record' || monitor.mode === 'start' + var point = { + coords: [49.2578298 + n,-123.2634732 + n], + direction: 90, + fov: 60, + range: 1, + title: `${monitor.name} (${monitor.host})`, + html: buildPinPopupHtml(monitor) + }; + if(!modeIsOn){ + + }else if(geolocation){ + var { + lat, + lng, + zoom, + direction, + fov, + range, + } = getGeolocationParts(monitor.details.geolocation); + point.direction = direction + point.fov = fov + point.range = range + point.coords = [lat, lng] + console.log(point) + points.push(point) + }else{ + n += 0.0001105; + points.push(point) + } + }) + return points + } + function plotPinsToMap(pins){ + for (var i = 0; i < pins.length; i++) { + var pin = pins[i]; + var lat = pin.coords[0]; + var lng = pin.coords[1]; + var html = pin.html; + L.marker([lat, lng], { title: pin.title }).bindPopup(html).addTo(loadedMap); + console.log(pin) + drawMapMarkerFov(loadedMap,{ + lat, + lng, + direction: pin.direction, + fov: pin.fov, + range: pin.range, + }); + } + } + function loadMap(){ + console.log('Load Map') + var monitorMapInfo = dashboardOptions().monitorMap || { + center: { lat:49.2578298, lng:-123.2634732 }, + zoom: 13 + }; + var center = monitorMapInfo.center + var lat = center.lat + var lng = center.lng + var zoom = monitorMapInfo.zoom + var monitorPins = getPinsFromMonitors() + loadedMap = L.map('monitor-map-canvas').setView([lat, lng], zoom); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + }).addTo(loadedMap); + loadedMap.on('moveend', function() { + saveCurrentPosition() + }); + loadedMap.on('popupopen', function(e) { + var popup = $('#leaflet-monitor-block') + var groupKey = popup.attr('data-ke') + var monitorId = popup.attr('data-mid') + var monitor = loadedMonitors[monitorId] + console.log(`loading `,monitorId,!!monitor) + loadPopupVideoList(monitor) + }); + plotPinsToMap(monitorPins) + } + function unloadMap(){ + loadedMap.remove(); + loadedMap = null; + } + function saveCurrentPosition(){ + var center = loadedMap.getCenter(); + var zoom = loadedMap.getZoom(); + dashboardOptions('monitorMap',{ + center, + zoom, + }) + } + addOnTabOpen('monitorMap', function () { + loadMap() + }) + addOnTabReopen('monitorMap', function () { + loadMap() + }) + addOnTabAway('monitorMap', function () { + unloadMap() + }) + onInitWebsocket(function (d){ + }) + onWebSocketEvent(function (d){ + switch(d.f){ + case'monitor_edit': + break; + case'monitor_status': + break; + } + }) +}) diff --git a/web/assets/js/bs5.monitorMap.utils.js b/web/assets/js/bs5.monitorMap.utils.js new file mode 100644 index 00000000..3b696ab6 --- /dev/null +++ b/web/assets/js/bs5.monitorMap.utils.js @@ -0,0 +1,89 @@ +function calculateFOV(camera, direction, fieldOfView, range) { + var startAngle = (direction - fieldOfView / 2) * Math.PI / 180; + var endAngle = (direction + fieldOfView / 2) * Math.PI / 180; + var sectorPoints = [camera]; + for (var angle = startAngle; angle <= endAngle; angle += Math.PI / 180) { + var dx = range * Math.cos(angle); + var dy = range * Math.sin(angle); + var lat = camera[0] + dy / 111.325; // Rough conversion from kilometers to degrees + var lng = camera[1] + dx / (111.325 * Math.cos(camera[0] * Math.PI / 180)); // Rough conversion from kilometers to degrees + sectorPoints.push([lat, lng]); + } + sectorPoints.push(camera); + return sectorPoints; +} +function drawMapMarkerFov(map, { + lat, + lng, + direction, + fov, + range +}){ + var fovEl = L.polygon(calculateFOV([lat, lng], direction, fov, range), {color: 'red'}).addTo(map); + return fovEl +} +function setMapMarkerFov(fovEl,{ + lat, + lng, + direction, + fov, + range, +}){ + fovEl.setLatLngs(calculateFOV([lat, lng], direction, fov, range)); +} + +function getGeolocationParts(geolocation){ + var defaultLat = 49.2578298 + var defaultLng = -123.2634732 + var defaultZoom = 13 + var defaultDirection = 90 + var defaultFov = 60 + var defaultRange = 1 + try{ + var parts = geolocation.split(',') + var lat = !parts[0] ? defaultLat : parseFloat(parts[0].trim().replace('@','')) || defaultLat + var lng = !parts[1] ? defaultLng : parseFloat(parts[1].trim()) || defaultLng + var zoom = !parts[2] ? defaultZoom : parseFloat(parts[2].trim().replace('v','')) || defaultZoom + var direction = !parts[3] ? defaultDirection : parseFloat(parts[3].trim()) || defaultDirection + var fov = !parts[4] ? defaultFov : parseFloat(parts[4].trim()) || defaultFov + var range = !parts[5] ? defaultRange : parseFloat(parts[5].trim()) || defaultRange + }catch(err){ + console.error(err) + var lat = defaultLat + var lng = defaultLng + var zoom = defaultZoom + var direction = defaultDirection + var fov = defaultFov + var range = defaultRange + } + return { + lat, + lng, + zoom, + direction, + fov, + range, + } +} + +function getCardinalDirection(degree) { + if (degree >= 337.5 || degree < 22.5) { + return 'N'; + } else if (degree >= 22.5 && degree < 67.5) { + return 'NE'; + } else if (degree >= 67.5 && degree < 112.5) { + return 'E'; + } else if (degree >= 112.5 && degree < 157.5) { + return 'SE'; + } else if (degree >= 157.5 && degree < 202.5) { + return 'S'; + } else if (degree >= 202.5 && degree < 247.5) { + return 'SW'; + } else if (degree >= 247.5 && degree < 292.5) { + return 'W'; + } else if (degree >= 292.5 && degree < 337.5) { + return 'NW'; + } else { + return 'Invalid degree'; + } +} diff --git a/web/assets/js/bs5.monitorSettings.js b/web/assets/js/bs5.monitorSettings.js index 6f4f1ba4..69099712 100644 --- a/web/assets/js/bs5.monitorSettings.js +++ b/web/assets/js/bs5.monitorSettings.js @@ -1,4 +1,8 @@ var monitorEditorSelectedMonitor = null +onMonitorSettingsLoadedExtensions = [] +function onMonitorSettingsLoaded(theAction){ + onMonitorSettingsLoadedExtensions.push(theAction) +} $(document).ready(function(e){ //Monitor Editor @@ -434,7 +438,8 @@ window.getMonitorEditFormFields = function(){ } if(monitorConfig.name == ''){errorsFound.push('Monitor Name cannot be blank')} //edit details - monitorConfig.details = safeJsonParse(monitorConfig.details) + monitorConfig.details = getDetailValues(editorForm) + // monitorConfig.details = safeJsonParse(monitorConfig.details) monitorConfig.details.substream = getSubStreamChannelFields() monitorConfig.details.input_map_choices = monitorSectionInputMapsave() // TODO : Input Maps and Stream Channels (does old way at the moment) @@ -463,12 +468,10 @@ function getAdditionalStreamChannelFields(tempID,channelId){ var fieldInfo = monitorSettingsAdditionalStreamChannelFieldHtml.replaceAll('$[TEMP_ID]',tempID).replaceAll('$[NUMBER]',channelId) return fieldInfo } - addOnTabOpen('monitorSettings', function () { setFieldVisibility() drawMonitorSettingsSubMenu() }) - addOnTabReopen('monitorSettings', function () { setFieldVisibility() drawMonitorSettingsSubMenu() @@ -688,6 +691,9 @@ function importIntoMonitorEditor(options){ monitorsForCopy.find('optgroup').html(tmp) setFieldVisibility() drawMonitorSettingsSubMenu() + onMonitorSettingsLoadedExtensions.forEach(function(theAction){ + theAction(monitorConfig) + }) } //parse "Automatic" field in "Input" Section monitorEditorWindow.on('change','.auto_host_fill input,.auto_host_fill select',function(e){ @@ -1014,9 +1020,9 @@ editorForm.find('[name="type"]').change(function(e){ var el = $(this); if(el.val()==='h264')editorForm.find('[name="protocol"]').val('rtsp').change() }) -editorForm.find('[detail]').change(function(){ - onDetailFieldChange(this) -}) +// editorForm.find('[detail]').change(function(){ +// onDetailFieldChange(this) +// }) editorForm.on('change','[selector]',function(){ var el = $(this); onSelectorChange(el,editorForm) diff --git a/web/assets/js/bs5.monitorSettings.monitorMap.js b/web/assets/js/bs5.monitorSettings.monitorMap.js new file mode 100644 index 00000000..3733b8dd --- /dev/null +++ b/web/assets/js/bs5.monitorSettings.monitorMap.js @@ -0,0 +1,150 @@ +$(document).ready(function(e){ + var monitorEditorWindow = $('#tab-monitorSettings') + var monitorSettingsMonitorMap = $('#monitor-settings-monitor-map') + var monitorSettingsMonitorMapContainer = $('#monitor-settings-monitor-map-container') + var monitorSettingsMapOptionsEl = $('#monitor-settings-geolocation-options') + var monitorSettingsMapOptionsElOptions = monitorSettingsMapOptionsEl.find('[map-option]') + var editorForm = monitorEditorWindow.find('form') + var loadedMap; + var monitorMapMarker; + var monitorMapMarkerFov; + function setAdditionalControls(options){ + options = options || {} + monitorSettingsMapOptionsElOptions.each(function(n,v){ + var el = $(v) + var key = el.attr('map-option') + if(options[key])el.val(options[key]); + }) + } + function setGeolocationFieldValue(markerDetails) { + editorForm.find(`[detail="geolocation"]`).val(getMapMarkerPosition(markerDetails)) + } + function loadMap(monitor, geoString){ + try{ + unloadMap() + }catch(err){ + + } + console.log('MAP LOAD!!!',monitor) + var { + lat, + lng, + zoom, + direction, + fov, + range, + } = getGeolocationParts(geoString || monitor.details.geolocation); + loadedMap = L.map('monitor-settings-monitor-map').setView([lat, lng], zoom); + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + }).addTo(loadedMap); + monitorMapMarker = L.marker([lat, lng], { + title: monitor ? `${monitor.name} (${monitor.host})` : null, + draggable: true, + }).addTo(loadedMap); + monitorMapMarker.on('dragend', function(){ + setGeolocationFieldValue() + }); + monitorMapMarker.on('drag', function(){ + var markerDetails = getMapMarkerDetails(); + setMapMarkerFov(monitorMapMarkerFov,markerDetails) + }); + loadedMap.on('zoomend', function(){ + setGeolocationFieldValue() + }); + setAdditionalControls({ + direction, + fov, + range, + }) + monitorMapMarkerFov = drawMapMarkerFov(loadedMap,{ + lat, + lng, + direction, + fov, + range, + }) + setAdditionalControlsUI({ + direction, + fov, + range, + }) + } + function unloadMap(){ + loadedMap.remove(); + loadedMap = null; + } + function getMapOptions(){ + var options = {} + monitorSettingsMapOptionsElOptions.each(function(n,v){ + var el = $(v) + var key = el.attr('map-option') + var value = el.val() + options[key] = parseFloat(value) || value + }) + return options + } + function getMapMarkerDetails(){ + var pos = monitorMapMarker.getLatLng() + var zoom = loadedMap.getZoom(); + var { + direction, + fov, + range, + } = getMapOptions(); + return { + lat: pos.lat, + lng: pos.lng, + zoom, + direction, + fov, + range, + } + } + function getMapMarkerPosition(markerDetails){ + var { + lat, + lng, + zoom, + direction, + fov, + range, + } = (markerDetails || getMapMarkerDetails()); + return `${lat},${lng},${zoom},${direction},${fov},${range}` + } + function setAdditionalControlsUI(markerDetails){ + $.each(markerDetails,function(key,value){ + var setValue = `${value}` + if(key === 'direction'){ + setValue = `${value} (${getCardinalDirection(value)})` + } + monitorSettingsMapOptionsEl.find(`[map-option-value="${key}"]`).text(setValue) + }) + } + editorForm.find(`[detail="geolocation"]`).change(function(){ + var geoString = $(this).val(); + var currentGeoString = monitorEditorSelectedMonitor.details.geolocation + if(!geoString && currentGeoString){ + editorForm.find(`[detail="geolocation"]`).val(currentGeoString) + } + loadMap(monitorEditorSelectedMonitor, geoString) + }) + addOnTabOpen('monitorSettings', function () { + loadMap(monitorEditorSelectedMonitor) + }) + addOnTabReopen('monitorSettings', function () { + loadMap(monitorEditorSelectedMonitor) + }) + addOnTabAway('monitorSettings', function () { + unloadMap() + }) + onMonitorSettingsLoaded(function(monitorConfig){ + loadMap(monitorConfig) + }) + monitorSettingsMapOptionsElOptions.on('input',function(){ + var markerDetails = getMapMarkerDetails(); + setGeolocationFieldValue(markerDetails) + setMapMarkerFov(monitorMapMarkerFov,markerDetails) + setAdditionalControlsUI(markerDetails) + }) +}) diff --git a/web/assets/js/bs5.monitorsUtils.js b/web/assets/js/bs5.monitorsUtils.js index f3257162..41756702 100644 --- a/web/assets/js/bs5.monitorsUtils.js +++ b/web/assets/js/bs5.monitorsUtils.js @@ -289,6 +289,12 @@ function buildStreamUrl(monitorId){ return streamURL } +function buildEmbedUrl(monitor){ + var monitorId = monitor.mid; + var streamURL = `${getApiPrefix(`embed`)}/${monitorId}/fullscreen|jquery|gui|relative?host=${location.pathname}` + return streamURL; +} + function getDbColumnsForMonitor(monitor){ var acceptedFields = [ 'mid', diff --git a/web/pages/blocks/footer.ejs b/web/pages/blocks/footer.ejs index a3f700b4..86b8b68f 100644 --- a/web/pages/blocks/footer.ejs +++ b/web/pages/blocks/footer.ejs @@ -1,4 +1,5 @@ + diff --git a/web/pages/blocks/header.ejs b/web/pages/blocks/header.ejs index d18d3614..b42b4e23 100644 --- a/web/pages/blocks/header.ejs +++ b/web/pages/blocks/header.ejs @@ -31,6 +31,7 @@ + diff --git a/web/pages/blocks/home/monitorMap.ejs b/web/pages/blocks/home/monitorMap.ejs new file mode 100644 index 00000000..74a26d0c --- /dev/null +++ b/web/pages/blocks/home/monitorMap.ejs @@ -0,0 +1,23 @@ +
+
+ <% + var drawBlock + var buildOptions + %> + <% + include fieldBuilders.ejs + %> + <% + var pageName = 'Monitor Map'; + Object.keys(define[pageName].blocks).forEach(function(blockKey){ + var pageLayout = define[pageName].blocks[blockKey] + drawBlock(pageLayout) + }) + %> + +
+ + + + +