From b2fc57175c95c50cd15a3e0460b2011762d71b5e Mon Sep 17 00:00:00 2001 From: Moe Date: Wed, 3 May 2023 19:08:42 +0000 Subject: [PATCH] Cycling View for Live Grid --- definitions/base.js | 43 ++++++-- languages/en_CA.json | 5 + libs/events/utils.js | 6 +- web/assets/js/bs5.dashboard-base.js | 4 + web/assets/js/bs5.liveGrid.cycle.js | 163 ++++++++++++++++++++++++++++ web/assets/js/bs5.liveGrid.js | 50 ++++++--- web/assets/js/bs5.monitorsUtils.js | 37 +++++++ web/assets/js/bs5.startup.js | 4 +- web/pages/blocks/footer.ejs | 1 + 9 files changed, 287 insertions(+), 26 deletions(-) create mode 100644 web/assets/js/bs5.liveGrid.cycle.js diff --git a/definitions/base.js b/definitions/base.js index a7956330..6244d93a 100644 --- a/definitions/base.js +++ b/definitions/base.js @@ -4869,6 +4869,32 @@ module.exports = function(s,config,lang){ "color": "forestgreen", "info": [] }, + "Live Grid": { + "name": lang['Live Grid'], + "color": "navy", + "info": [ + { + "field": lang['Monitors per row'], + "placeholder": "3", + attribute:'localStorage="montage"', + }, + { + "field": lang['Cycle Monitors per row'], + "placeholder": "2", + attribute:'localStorage="cycleLivePerRow"', + }, + { + "field": lang['Number of Cycle Monitors'], + "placeholder": "4", + attribute:'localStorage="cycleLiveNumberOfMonitors"', + }, + { + "field": lang['Cycle Monitor Height'], + "placeholder": "4", + attribute:'localStorage="cycleLiveMonitorHeight"', + }, + ] + }, "Preferences": { "name": lang.Preferences, "color": "navy", @@ -4914,15 +4940,6 @@ module.exports = function(s,config,lang){ } ] }, - { - "field": lang['Monitors per row'], - "placeholder": "3", - attribute:'localStorage="montage"', - "description": "", - "default": "", - "example": "", - "possible": "" - }, { "field": lang['Browser Console Log'], attribute:'localStorage="browserLog"', @@ -4969,7 +4986,7 @@ module.exports = function(s,config,lang){ "possible": s.listOfThemes }, ] - } + }, } }, "ONVIF Device Manager": { @@ -7637,6 +7654,12 @@ module.exports = function(s,config,lang){ attributes: 'shinobi-switch="monitorMuteAudio" ui-change-target=".dot" on-class="dot-green" off-class="dot-grey"', color: 'grey', }, + { + label: lang['Cycle Monitors'], + class: 'cursor-pointer', + attributes: 'shinobi-switch="cycleLiveGrid" ui-change-target=".dot" on-class="dot-green" off-class="dot-grey"', + color: 'grey', + }, { label: lang['JPEG Mode'], class: 'cursor-pointer', diff --git a/languages/en_CA.json b/languages/en_CA.json index b5382d9b..9667671e 100644 --- a/languages/en_CA.json +++ b/languages/en_CA.json @@ -40,6 +40,11 @@ "Use Raw Snapshot": "Use Raw Snapshot", "Failed to Edit Account": "Failed to Edit Account", "How to Connect": "How to Connect", + "Cycle": "Cycle", + "Cycle Monitor Height": "Cycle Monitor Height", + "Number of Cycle Monitors": "Number of Cycle Monitors", + "Cycle Monitors": "Cycle Monitors", + "Cycle Monitors per row": "Cycle Monitors per row", "Login": "Login", "Room ID": "Room ID", "Substream": "Substream", diff --git a/libs/events/utils.js b/libs/events/utils.js index 6a8c586e..03325bad 100644 --- a/libs/events/utils.js +++ b/libs/events/utils.js @@ -464,8 +464,10 @@ module.exports = (s,config,lang) => { } if( - filter.command || - (monitorDetails.detector_command_enable === '1' && !s.group[d.ke].activeMonitors[monitorId].detector_command) + filter.command || ( + monitorDetails.detector_command_enable === '1' && + !s.group[d.ke].activeMonitors[monitorId].detector_command + ) ){ s.group[d.ke].activeMonitors[monitorId].detector_command = s.createTimeout('detector_command',s.group[d.ke].activeMonitors[monitorId],monitorDetails.detector_command_timeout,10) var detector_command = addEventDetailsToString(d,monitorDetails.detector_command) diff --git a/web/assets/js/bs5.dashboard-base.js b/web/assets/js/bs5.dashboard-base.js index eca99660..fc238696 100644 --- a/web/assets/js/bs5.dashboard-base.js +++ b/web/assets/js/bs5.dashboard-base.js @@ -21,6 +21,7 @@ var chartColors = { var isAppleDevice = navigator.userAgent.match(/(iPod|iPhone|iPad)/)||(navigator.userAgent.match(/(Safari)/)&&!navigator.userAgent.match('Chrome')); var isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4)) +var keyShortcuts = {} function base64ArrayBuffer(arrayBuffer) { var base64 = '' var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' @@ -1000,6 +1001,9 @@ function convertJsonToAccordionHtml(theJson){ finalHtml = recurseJson(theJson,false) return finalHtml } +function findCommonElements(array1, array2) { + return array2.filter(item => array1.includes(item)); +} // on page load var readyFunctions = [] function onDashboardReady(theAction){ diff --git a/web/assets/js/bs5.liveGrid.cycle.js b/web/assets/js/bs5.liveGrid.cycle.js new file mode 100644 index 00000000..2c3bdd91 --- /dev/null +++ b/web/assets/js/bs5.liveGrid.cycle.js @@ -0,0 +1,163 @@ +var liveGridCycleTimer = null; +var cycleLiveOptionsBefore = null; +var cycleLiveOptions = null; +var cycleLiveMoveNext = function(){} +var cycleLiveMovePrev = function(){} +var cycleLiveFullList = null +var cycleLiveCurrentPart = null +function getListOfMonitorsToCycleLive(chosenTags,useMonitorIds){ + var monitors = [] + if(useMonitorIds){ + monitors = getMonitorsFromIds(chosenTags) + }else if(chosenTags){ + var tags = sanitizeTagList(chosenTags) + monitors = getMonitorsFromTags(tags) + }else{ + monitors = getRunningMonitors(true) + } + return monitors; +} +function getPartForCycleLive(fullList, afterMonitorId, numberOfMonitors) { + const startIndex = afterMonitorId ? fullList.findIndex(monitor => monitor.mid === afterMonitorId) : -1; + const result = []; + for (let i = 1; i <= numberOfMonitors; i++) { + const index = (startIndex + i) % fullList.length; + result.push(fullList[index]); + } + return result; +} +function displayCycleSetOnLiveGrid(monitorsList){ + cycleLiveCurrentPart = [].concat(monitorsList) + closeAllLiveGridPlayers() + monitorsWatchOnLiveGrid(monitorsList.map(monitor => monitor.mid)) +} +// rotator +function stopCycleLive(){ + console.error('STOP!!!!!',new Error) + clearTimeout(liveGridCycleTimer) + liveGridCycleTimer = null +} +function resumeCycleLive(fullList,partForCycle,numberOfMonitors){ + console.error('RESUME!!!!!',new Error) + function next(){ + var afterMonitorId = partForCycle.slice(-1)[0].mid; + partForCycle = getPartForCycleLive(fullList,afterMonitorId,numberOfMonitors) + displayCycleSetOnLiveGrid(partForCycle) + reset() + } + function prev(){ + var firstInPart = partForCycle[0].mid; + var firstPartIndex = fullList.findIndex(monitor => monitor.mid === firstInPart) + var backedToIndex = (firstPartIndex - (numberOfMonitors + 1) + fullList.length) % fullList.length; + var beforeMonitorId = fullList[backedToIndex].mid + partForCycle = getPartForCycleLive(fullList,beforeMonitorId,numberOfMonitors, true) + displayCycleSetOnLiveGrid(partForCycle) + reset() + } + function reset(){ + clearTimeout(liveGridCycleTimer) + liveGridCycleTimer = setTimeout(function(){ + next() + },30000) + } + reset() + cycleLiveMoveNext = next + cycleLiveMovePrev = prev +} +function beginCycleLive({ + chosenTags, + useMonitorIds, + numberOfMonitors, + monitorHeight, +}){ + var fullList = getListOfMonitorsToCycleLive(chosenTags,useMonitorIds) + var partForCycle = getPartForCycleLive(fullList,null,numberOfMonitors) + cycleLiveFullList = [].concat(fullList) + displayCycleSetOnLiveGrid(partForCycle) + stopCycleLive() + resumeCycleLive(fullList,partForCycle,numberOfMonitors) +} +dashboardSwitchCallbacks.cycleLiveGrid = function(toggleState){ + if(toggleState !== 1){ + cycleLiveOptions = null + cycleLiveOptionsBefore = null + stopCycleLive() + }else{ + openTab('liveGrid') + cycleLiveOptionsBefore = cycleLiveOptions ? Object.assign({},cycleLiveOptions) : null + const theLocalStorage = dashboardOptions() + const cycleLivePerRow = parseInt(theLocalStorage.cycleLivePerRow) || 2 + const cycleLiveNumberOfMonitors = parseInt(theLocalStorage.cycleLiveNumberOfMonitors) || 4 + const cycleLiveMonitorHeight = parseInt(theLocalStorage.cycleLiveMonitorHeight) || 4 + cycleLiveOptions = { + chosenTags: null, + useMonitorIds: null, + monitorsPerRow: cycleLivePerRow, + numberOfMonitors: cycleLiveNumberOfMonitors, + monitorHeight: cycleLiveMonitorHeight, + } + beginCycleLive(cycleLiveOptions) + } +} +function keyShortcutsForCycleLive(enable) { + function cleanup(){ + document.removeEventListener('keydown', keyShortcuts['cycleLive'].keydown); + document.removeEventListener('keyup', keyShortcuts['cycleLive'].keyup); + delete(keyShortcuts['cycleLive']) + } + if(enable){ + let isKeyPressed = false; + function handleKeyboard(event){ + if (isKeyPressed) { + return; + } + event.preventDefault(); + switch(event.code){ + case 'Space': + isKeyPressed = true; + if(liveGridCycleTimer){ + stopCycleLive() + }else{ + resumeCycleLive( + cycleLiveFullList, + cycleLiveCurrentPart, + cycleLiveOptions.numberOfMonitors + ) + } + break; + case 'ArrowLeft': + isKeyPressed = true; + cycleLiveMovePrev(); + break; + case 'ArrowRight': + isKeyPressed = true; + cycleLiveMoveNext(); + break; + } + } + function handleKeyup(event) { + isKeyPressed = false; + } + keyShortcuts['cycleLive'] = { + keydown: handleKeyboard, + keyup: handleKeyup, + } + document.addEventListener('keydown', keyShortcuts['cycleLive'].keydown); + document.addEventListener('keyup', keyShortcuts['cycleLive'].keyup); + }else{ + cleanup() + } +} +addOnTabOpen('liveGrid', function () { + keyShortcutsForCycleLive(true) +}) +addOnTabReopen('liveGrid', function () { + if(cycleLiveOptions){ + beginCycleLive(cycleLiveOptions) + } + keyShortcutsForCycleLive(true) +}) +addOnTabAway('liveGrid', function () { + stopCycleLive() + keyShortcutsForCycleLive(false) +}) diff --git a/web/assets/js/bs5.liveGrid.js b/web/assets/js/bs5.liveGrid.js index 11f91d58..06c4a681 100644 --- a/web/assets/js/bs5.liveGrid.js +++ b/web/assets/js/bs5.liveGrid.js @@ -280,7 +280,7 @@ function isLiveGridLogStreamOpenBefore(monitorId){ var liveGridLogStreams = dashboardOptions().liveGridLogStreams || {} return liveGridLogStreams[monitorId] } -function drawLiveGridBlock(monitorConfig,subStreamChannel){ +function drawLiveGridBlock(monitorConfig,subStreamChannel,forcedMonitorsPerRow,monitorHeight){ var monitorId = monitorConfig.mid if($('#monitor_live_' + monitorId).length === 0){ var x = null; @@ -292,7 +292,19 @@ function drawLiveGridBlock(monitorConfig,subStreamChannel){ var html = buildLiveGridBlock(monitorConfig) var monitorOrderEngaged = dashboardOptions().switches.monitorOrder === 1; var wasLiveGridLogStreamOpenBefore = isLiveGridLogStreamOpenBefore(monitorId) - if(monitorOrderEngaged && $user.details.monitorOrder && $user.details.monitorOrder[monitorConfig.ke+''+monitorId]){ + if(forcedMonitorsPerRow){ + var windowHeight = $(window).height() + var legend = { + "1": 12, + "2": {w: 6, h: 4}, + "3": 4, + "4": 3, + "6": 2, + } + var dimensionsConverted = legend[`${forcedMonitorsPerRow}`] || legend["2"]; + width = dimensionsConverted.w ? dimensionsConverted.w : dimensionsConverted; + height = monitorHeight ? monitorHeight : dimensionsConverted.h ? dimensionsConverted.h : dimensionsConverted; + }else if(monitorOrderEngaged && $user.details.monitorOrder && $user.details.monitorOrder[monitorConfig.ke+''+monitorId]){ var saved = $user.details.monitorOrder[monitorConfig.ke+''+monitorId]; x = saved.x; y = saved.y; @@ -303,7 +315,7 @@ function drawLiveGridBlock(monitorConfig,subStreamChannel){ x, y, h: isSmallMobile ? 1 : height, - w: isSmallMobile ? 4 : width,// !monitorOrderEngaged + w: isSmallMobile ? 4 : width, content: html }); if(isMobile)liveGridData.disable(); @@ -642,6 +654,14 @@ function closeLiveGridPlayer(monitorId,killElement){ console.log(err) } } +function monitorWatchOnLiveGrid(monitorId, watchOff){ + return mainSocket.f({f:'monitor',ff:watchOff ? 'watch_off' : 'watch_on',id: monitorId}) +} +function monitorsWatchOnLiveGrid(monitorIds, watchOff){ + monitorIds.forEach((monitorId) => { + monitorWatchOnLiveGrid(monitorId, watchOff) + }) +} function callMonitorToLiveGrid(v){ var watchedOn = dashboardOptions().watch_on || {} if(watchedOn[v.ke] && watchedOn[v.ke][v.mid] === 1 && loadedMonitors[v.mid] && loadedMonitors[v.mid].mode !== 'stop'){ @@ -668,14 +688,16 @@ function loadPreviouslyOpenedLiveGridBlocks(){ } function closeAllLiveGridPlayers(rememberClose){ $.each(loadedMonitors,function(monitorId,monitor){ - mainSocket.f({ - f: 'monitor', - ff: 'watch_off', - id: monitor.mid - }) - setTimeout(function(){ - saveLiveGridBlockOpenState(monitorId,$user.ke,0) - },1000) + if(loadedLiveGrids[monitorId]){ + mainSocket.f({ + f: 'monitor', + ff: 'watch_off', + id: monitor.mid + }) + setTimeout(function(){ + saveLiveGridBlockOpenState(monitorId,$user.ke,0) + },1000) + } }) } function saveLiveGridBlockOpenState(monitorId,groupKey,state){ @@ -1155,13 +1177,15 @@ $(document).ready(function(e){ var monitorId = d.mid || d.id var loadedMonitor = loadedMonitors[monitorId] var subStreamChannel = d.subStreamChannel + var monitorsPerRow = cycleLiveOptions ? cycleLiveOptions.monitorsPerRow : null; + var monitorHeight = cycleLiveOptions ? cycleLiveOptions.monitorHeight : null; if(!loadedMonitor.subStreamChannel && loadedMonitor.details.stream_type === 'useSubstream'){ toggleSubStream(monitorId,function(){ - drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel) + drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel,monitorsPerRow,monitorHeight) saveLiveGridBlockOpenState(monitorId,$user.ke,1) }) }else{ - drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel) + drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel,monitorsPerRow,monitorHeight) saveLiveGridBlockOpenState(monitorId,$user.ke,1) } break; diff --git a/web/assets/js/bs5.monitorsUtils.js b/web/assets/js/bs5.monitorsUtils.js index 8c473a18..fef182e1 100644 --- a/web/assets/js/bs5.monitorsUtils.js +++ b/web/assets/js/bs5.monitorsUtils.js @@ -679,6 +679,13 @@ function muteMonitorAudio(monitorId,buttonEl){ var volumeIcon = monitorMutes[monitorId] !== 1 ? 'volume-up' : 'volume-off' if(buttonEl)buttonEl.find('i').removeClass('fa-volume-up fa-volume-off').addClass('fa-' + volumeIcon) } +function getMonitorsFromIds(monitorIds){ + var foundMonitors = [] + monitorIds.forEach((monitorId) => { + foundMonitors.push(loadedMonitors[monitorId]) + }) + return foundMonitors +} function getListOfTagsFromMonitors(){ var listOftags = {} $.each(loadedMonitors,function(monitorId,monitor){ @@ -691,6 +698,24 @@ function getListOfTagsFromMonitors(){ }) return listOftags } +function sanitizeTagList(tags){ + var allTags = getListOfTagsFromMonitors() + return findCommonElements(allTags,tags) +} +function getMonitorsFromTags(tags){ + var foundMonitors = {} + $.each(loadedMonitors,function(monitorId,monitor){ + if(monitor.tags){ + tags.forEach((tag) => { + if(monitor.tags.includes(tag)){ + if(!foundMonitors[monitorId])foundMonitors[monitorId] = monitor + } + }) + + } + }) + return Object.values(foundMonitors) +} function buildMonitorGroupListFromTags(){ var html = `` var listOftags = getListOfTagsFromMonitors() @@ -997,6 +1022,18 @@ function getRowsMonitorId(rowEl){ function getMonitorEmbedLink(monitorConfig){ return `${getApiPrefix('embed')}/${monitorConfig.mid}/fullscreen|jquery|relative` } +function getRunningMonitors(asArray){ + const foundMonitors = {} + $.each(loadedMonitors,function(monitorId,monitor){ + if( + monitor.mode === 'start' || + monitor.mode === 'record' + ){ + foundMonitors[monitorId] = monitor + } + }) + return asArray ? Object.values(foundMonitors) : foundMonitors +} $(document).ready(function(){ $('body') .on('click','[system]',function(){ diff --git a/web/assets/js/bs5.startup.js b/web/assets/js/bs5.startup.js index 87c3b5b2..34f4293d 100644 --- a/web/assets/js/bs5.startup.js +++ b/web/assets/js/bs5.startup.js @@ -229,7 +229,6 @@ onWebSocketEvent(function (d){ }) $(document).ready(function(){ loadHiddenSectionsInForms() - loadSwitchStates() loadClassToggleStates() loadDropdownToggleStates() loadLocalStorageInputValues() @@ -249,3 +248,6 @@ $(window).focus(function() { }).blur(function() { windowFocus = false }) +onDashboardReady(function(){ + loadSwitchStates() +}) diff --git a/web/pages/blocks/footer.ejs b/web/pages/blocks/footer.ejs index b43a26df..8f24d096 100644 --- a/web/pages/blocks/footer.ejs +++ b/web/pages/blocks/footer.ejs @@ -32,6 +32,7 @@ +