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 @@
+