add Power Video Viewer to Dashboard v3

Its not actually needed anymore now that Videos rows display events and snapshots within them.
email-send-options-from-account
Moe 2021-10-23 22:39:05 -07:00
parent 414f27e9ee
commit 23cbb76416
9 changed files with 1413 additions and 192 deletions

View File

@ -7608,6 +7608,11 @@ module.exports = function(s,config,lang){
<span class="badge bg-light text-dark rounded-pill align-text-bottom cameraCount"><i class="fa fa-spinner fa-pulse"></i></span>`,
pageOpen: 'monitorsList',
},
{
icon: 'map-marker',
label: `${lang['Power Viewer']}`,
pageOpen: 'powerVideo',
},
{
icon: 'wrench',
label: `${lang['Monitor Settings']}`,
@ -7792,5 +7797,203 @@ module.exports = function(s,config,lang){
}
}
},
"Power Viewer": {
"section": lang["Power Viewer"],
"blocks": {
"Search Settings": {
id: "powerVideoTabs",
"color": "blue",
noHeader: true,
attribute: `tab-chooser-parent`,
"section-pre-class": "col-md-4",
"info": [
{
"color": "blue",
noHeader: true,
isSection: true,
isFormGroupGroup: true,
"info": [
{
"fieldType": "btn-group",
"btns": [
{
"fieldType": "btn",
"class": `btn-primary btn-sm`,
"attribute": `tab-chooser="monitors"`,
"btnContent": `${lang['Monitors']}`,
},
{
"fieldType": "btn",
"class": `btn-primary btn-sm`,
"attribute": `tab-chooser="settings"`,
"btnContent": `${lang['Search Settings']}`,
},
],
},
]
},
{
"name": lang["Monitors"],
"color": "blue",
noId: true,
isFormGroupGroup: true,
attribute: `tab-section="monitors"`,
"info": [
{
"id": "powerVideoMonitorsList",
"fieldType": "div",
"class": "list-group",
},
]
},
{
hidden: true,
"name": lang["Search Settings"],
"color": "blue",
noId: true,
isFormGroupGroup: true,
attribute: `tab-section="settings"`,
"info": [
{
"id": "powerVideoDateRange",
"field": lang['Date Range'],
},
{
"id": "powerVideoVideoLimit",
"field": lang['Video Limit'] + ` (${lang['Per Monitor']})`,
"placeholder": "0",
},
{
"id": "powerVideoEventLimit",
"field": lang['Event Limit'] + ` (${lang['Per Monitor']})`,
"placeholder": "500",
},
{
id:'powerVideoSet',
field: lang['Video Set'],
default:'h264',
"fieldType": "select",
possible:[
{
"name": lang.Local,
"value": "local"
},
{
"name": lang.Cloud,
"value": "cloud"
},
]
},
]
},
]
},
"Video Playback": {
id: "powerVideoVideoPlayback",
noHeader: true,
"color": "green",
"section-pre-class": "col-md-8 search-parent",
"info": [
{
"id": "powerVideoMonitorViews",
"fieldType": "div",
},
{
"id": "powerVideoMonitorControls",
"color": "blue",
noHeader: true,
isSection: true,
isFormGroupGroup: true,
'section-class': 'text-center',
"info": [
{
"fieldType": "btn-group",
"btns": [
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="toggleZoom" title="${lang['Zoom In']}"`,
"btnContent": `<i class="fa fa-search-plus"></i>`,
},
],
},
{
"fieldType": "btn-group",
"btns": [
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="previousVideoAll" title="${lang['Previous Video']}"`,
"btnContent": `<i class="fa fa-arrow-circle-left"></i>`,
},
{
"fieldType": "btn",
"class": `btn-danger btn-sm`,
"attribute": `powerVideo-control="playAll" title="${lang['Play']}"`,
"btnContent": `<i class="fa fa-play"></i>`,
},
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="pauseAll" title="${lang['Pause']}"`,
"btnContent": `<i class="fa fa-pause"></i>`,
},
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="nextVideoAll" title="${lang['Next Video']}"`,
"btnContent": `<i class="fa fa-arrow-circle-right"></i>`,
},
],
},
{
"fieldType": "btn-group",
"style": "font-family: monospace;",
"btns": [
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="playSpeedAll" data-speed="1"`,
"btnContent": `1`,
},
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="playSpeedAll" data-speed="5"`,
"btnContent": `5`,
},
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="playSpeedAll" data-speed="10"`,
"btnContent": `10`,
},
{
"fieldType": "btn",
"class": `btn-default btn-sm`,
"attribute": `powerVideo-control="playSpeedAll" data-speed="15"`,
"btnContent": `15`,
},
],
},
]
},
]
},
"Time Strip": {
id: "powerVideoTimelineStripsContainer",
noHeader: true,
"color": "green",
"section-pre-class": "col-md-12 mt-3",
"info": [
{
"id": "powerVideoTimelineStrips",
"fieldType": "div",
"divContent": `<div class="loading"><i class="fa fa-hand-pointer-o"></i><div class="epic-text">${lang['Select a Monitor']}</div></div>`,
},
]
}
}
},
})
}

View File

@ -1241,5 +1241,6 @@
"Motion Threshold":"Motion Threshold",
"Attach Snapshot": "Attach Snapshot",
"Invalid Settings": "Invalid Settings",
"Detection": "Detection"
"Detection": "Detection",
"Cloud": "Cloud"
}

View File

@ -0,0 +1,177 @@
#powerVideo .videoPlayer {
text-align: center;
display: inline-block;
position: relative;
}
#powerVideo .videoPlayer video{
max-width: 100%;
height: 300px;
object-fit: fill;
}
#powerVideo .videoPlayer:fullscreen video{
height: 100%;
max-height: 100%;
}
#powerVideoMonitorControls{
border-radius: 0 0 5px 5px;
padding: 5px;
background: #222;
margin: 0;
}
#powerVideoMonitorsList{
margin: 0;
}
#powerVideoMonitorsList .list-item{
cursor: pointer;
}
#powerVideoMonitorViews {
text-align: center;
min-height: 300px;
background: #444;
border-radius: 5px 5px 0 0;
overflow: hidden;
}
#powerVideo .videoPlayer .videoPlayer-detection-info {
position: absolute;
padding: 20px 10px 20px 10px;
height: 100%;
width: 100%;
top: 0;
left: 0;
margin: auto;
z-index: 11;
opacity: 0;
background: rgba(0,0,0,0.7);
color: #fff;
font-family: monospace;
overflow: auto;
text-align: left;
}
#powerVideo .videoPlayer:hover .videoPlayer-detection-info,
#powerVideo .videoPlayer.show-detection-info .videoPlayer-detection-info {
opacity: 1
}
#powerVideo .videoPlayer .videoPlayer-stream-objects {
position: absolute;
width: 100%;
height: 100%;
left: 0;
right: 0;
margin: auto;
z-index: 10;
}
#powerVideo .videoPlayer .videoPlayer-detection-info-object div {
padding-left: 5px;
}
#powerVideo .videoPlayer .videoPlayer-detection-info-object {
text-align: left
}
.videoPlayer-stream-objects .tag {
position: absolute;
bottom: 100%;
left: 0;
background: red;
color: #fff;
font-family: monospace;
font-size: 80%;
border-radius: 5px 5px 0 0;
padding: 3px 5px;
}
.videoPlayer-stream-objects .stream-detected-object {
position: absolute;
top: 0;
left: 0;
border: 3px solid red;
background: transparent;
border-radius: 5px
}
.videoPlayer-stream-objects .stream-detected-point {
position: absolute;
top: 0;
left: 0;
border: 3px solid yellow;
background: transparent;
border-radius: 5px
}
.videoPlayer-stream-objects .point {
position: absolute;
top: 0;
left: 0;
border: 3px solid red;
border-radius: 50%
}
/* loading */
#powerVideo .loading {
font-size: 20pt;
text-align: center;
}
#powerVideo .loading > div {
margin-top: 5px
}
/* VIS.js */
#powerVideo video {
width: 100%;
padding: 6px 0 0 0
}
#powerVideo .videoAfter,
#powerVideo .videoBefore {
display: none;
}
#powerVideo .vis-timeline {
font-family: monospace;
border-radius: 5px;
border-color: #172b4d;
}
#powerVideo .vis-item {
border-color: #347af1;
background-color: #4d87d0;
color: #fff;
}
#powerVideo .vis-item.vis-selected {
border-color: #f5365c;
background-color: #f5365c;
color: #fff;
}
#powerVideo .vis-panel.vis-bottom {
border: 1px #17294b;
}
#powerVideo .vis-time-axis .vis-grid.vis-minor{
border-color: #1a539a;
}
#powerVideo .vis-time-axis .vis-grid.vis-major {
border-color: #4d87d0;
}
#powerVideo .vis-time-axis .vis-text {
color: #fff;
}
[timeline-video-file] .progress{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
opacity: 0;
}
.vis-selected [timeline-video-file] .progress{
opacity: 1;
}

View File

@ -851,6 +851,17 @@ $(document).ready(function(){
else
$(this).show();
});
})
.on('click','[tab-chooser]',function(){
var el = $(this)
var parent = el.parents('[tab-chooser-parent]')
var tabName = el.attr('tab-chooser')
var allTabChoosersInParent = parent.find('[tab-chooser]')
var allTabsInParent = parent.find('[tab-section]')
allTabsInParent.hide()
allTabChoosersInParent.removeClass('active')
el.addClass('active')
parent.find(`[tab-section="${tabName}"]`).show()
});
$('.logout').click(function(e){
$.get(getApiPrefix() + '/logout/' + $user.ke + '/' + $user.uid,function(data){

View File

@ -553,6 +553,120 @@ function buildDefaultMonitorMenuItems(){
<li><a class="dropdown-item cursor-pointer" set-mode="start">${lang['Watch-Only']}</a></li>
<li><a class="dropdown-item cursor-pointer" set-mode="record">${lang.Record}</a></li>`
}
function magnifyStream(options){
if(!options.p && !options.parent){
var el = $(this),
parent = el.parents('[mid]')
}else{
parent = options.p || options.parent
}
if(!options.attribute){
options.attribute = ''
}
if(options.animate === true){
var zoomGlassAnimate = 'animate'
}else{
var zoomGlassAnimate = 'css'
}
if(!options.magnifyOffsetElement){
options.magnifyOffsetElement = '.stream-block'
}
if(!options.targetForZoom){
options.targetForZoom = '.stream-element'
}
if(options.auto === true){
var streamBlockOperator = 'position'
}else{
var streamBlockOperator = 'offset'
}
var magnifiedElement
if(!options.videoUrl){
if(options.useCanvas === true){
magnifiedElement = 'canvas'
}else{
magnifiedElement = 'iframe'
}
}else{
magnifiedElement = 'video'
}
if(!options.mon && !options.monitor){
var groupKey = parent.attr('ke')//group key
var monitorId = parent.attr('mid')//monitor id
var sessionKey = parent.attr('auth')//authkey
var monitor = $.ccio.mon[groupKey + monitorId + sessionKey]//monitor configuration
}else{
var monitor = options.mon || options.monitor
var groupKey = monitor.ke//group key
var monitorId = monitor.mid//monitor id
var sessionKey = monitor.auth//authkey
}
if(options.zoomAmount)zoomAmount = 3
if(!zoomAmount)zoomAmount = 3
var realHeight = parent.attr('realHeight')
var realWidth = parent.attr('realWidth')
var height = parseFloat(realHeight) * zoomAmount//height of stream
var width = parseFloat(realWidth) * zoomAmount//width of stream
var targetForZoom = parent.find(options.targetForZoom)
zoomGlass = parent.find(".zoomGlass")
var zoomFrame = function(){
var magnify_offset = parent.find(options.magnifyOffsetElement)[streamBlockOperator]()
var mx = options.pageX - magnify_offset.left
var my = options.pageY - magnify_offset.top
var rx = Math.round(mx/targetForZoom.width()*width - zoomGlass.width()/2)*-1
var ry = Math.round(my/targetForZoom.height()*height - zoomGlass.height()/2)*-1
var px = mx - zoomGlass.width()/2
var py = my - zoomGlass.height()/2
zoomGlass[zoomGlassAnimate]({left: px, top: py}).find(magnifiedElement)[zoomGlassAnimate]({left: rx, top: ry})
}
var commit = function(height,width){
zoomGlass.find(magnifiedElement).css({
height: height,
width: width
})
zoomFrame()
}
if(!height || !width || zoomGlass.length === 0){
zoomGlass = parent.find(".zoomGlass")
var zoomGlassShell = function(contents){return `<div ${options.attribute} class="zoomGlass">${contents}</div>`}
if(!options.videoUrl){
$.ccio.snapshot(monitor,function(url,buffer,w,h){
parent.attr('realWidth',w)
parent.attr('realHeight',h)
if(zoomGlass.length === 0){
if(options.useCanvas === true){
parent.append(zoomGlassShell('<canvas class="blenderCanvas"></canvas>'))
}else{
parent.append(zoomGlassShell('<iframe src="'+getApiPrefix('embed')+'/'+monitorId+'/fullscreen|jquery|relative"/><div class="hoverShade"></div>'))
}
zoomGlass = parent.find(".zoomGlass")
}
commit(h,w)
})
}else{
if(zoomGlass.length === 0){
parent.append(zoomGlassShell(`<video src="${options.videoUrl}" preload></video>`))
}
if(options.setTime){
var video = zoomGlass.find('video')[0]
video.currentTime = options.setTime
height = video.videoHeight
width = video.videoWidth
parent.attr('realWidth',width)
parent.attr('realHeight',height)
}
commit(height,width)
}
}else{
if(options.setTime){
var video = zoomGlass.find('video')
var src = video.attr('src')
video[0].currentTime = options.setTime
if(options.videoUrl !== src)zoomGlass.html(`<video src="${options.videoUrl}" preload></video>`)
}
commit(height,width)
}
}
$(document).ready(function(){
$('body')
.on('click','[system]',function(){

View File

@ -0,0 +1,679 @@
$(document).ready(function(e){
var powerVideoWindow = $('#powerVideo')
var powerVideoMonitorsListElement = $('#powerVideoMonitorsList')
var powerVideoMonitorViewsElement = $('#powerVideoMonitorViews')
var powerVideoTimelineStripsContainer = $('#powerVideoTimelineStrips')
var powerVideoDateRangeElement = $('#powerVideoDateRange')
var powerVideoVideoLimitElement = $('#powerVideoVideoLimit')
var powerVideoEventLimitElement = $('#powerVideoEventLimit')
var powerVideoSet = $('#powerVideoSet')
var powerVideoLoadedVideos = {}
var powerVideoLoadedEvents = {}
var powerVideoLoadedChartData = {}
var loadedTableGroupIds = {}
var eventsLabeledByTime = {}
var monitorSlotPlaySpeeds = {}
var currentlyPlayingVideos = {}
var extenders = {
onVideoPlayerTimeUpdateExtensions: [],
onVideoPlayerTimeUpdate: function(extender){
extenders.onVideoPlayerTimeUpdateExtensions.push(extender)
},
onVideoPlayerCreateExtensions: [],
onVideoPlayerCreate: function(extender){
extenders.onVideoPlayerCreateExtensions.push(extender)
},
}
var activeTimeline = null
// fix utc/localtime translation (use timelapseJpeg as guide, it works as expected) >
powerVideoDateRangeElement.daterangepicker({
startDate: moment().subtract(moment.duration("24:00:00")),
endDate: moment().add(moment.duration("24:00:00")),
timePicker: true,
timePicker24Hour: true,
timePickerSeconds: true,
timePickerIncrement: 30,
locale: {
format: 'DD/MM/YYYY h:mm A'
}
},function(start, end, label){
// $.pwrvid.drawTimeline()
powerVideoDateRangeElement.focus()
getSelectedMonitors().each(function(n,activeElement){
var monitorId = $(activeElement).attr('data-monitor')
requestTableData(monitorId)
})
});
// fix utc/localtime translation (use timelapseJpeg as guide, it works as expected) />
var loadVideosToTimeLineMemory = function(monitorId,videos,events){
powerVideoLoadedVideos[monitorId] = videos
powerVideoLoadedEvents[monitorId] = events
}
var drawMonitorsList = function(){
var html = ''
$.each(loadedMonitors,function(n,monitor){
html += `<div class="list-group-item" data-monitor="${monitor.mid}">${monitor.name}</div>`
})
powerVideoMonitorsListElement.html(html)
}
var requestTableData = function(monitorId,user){
if(!user)user = $user
var dateData = powerVideoDateRangeElement.data('daterangepicker')
mainSocket.f({
f: 'monitor',
ff: 'get',
fff: 'videos&events',
videoSet: powerVideoSet.val() || '',
videoLimit: parseInt(powerVideoVideoLimitElement.val()) || 0,
eventLimit: parseInt(powerVideoEventLimitElement.val()) || 500,
startDate: dateData.startDate.clone().utc().format('YYYY-MM-DDTHH:mm:ss'),
endDate: dateData.endDate.clone().utc().format('YYYY-MM-DDTHH:mm:ss'),
ke: user.ke,
mid: monitorId
})
}
var unloadTableData = function(monitorId,user){
if(!user)user = $user
delete(powerVideoLoadedVideos[monitorId])
delete(powerVideoLoadedEvents[monitorId])
delete(loadedTableGroupIds[monitorId])
delete(loadedTableGroupIds[monitorId + '_events'])
powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid="${monitorId}"]`).remove()
drawLoadedTableData()
}
var checkEventsAgainstVideo = function(video,events){
var videoStartTime = new Date(video.time)
var videoEndTime = new Date(video.end)
var eventsToCheck = events
video.detections = {}
var newSetOfEventsWithoutChecked = {}
$.each(eventsToCheck,function(n,event){
var eventTime = new Date(event.time)
var seekPosition = (eventTime - videoStartTime) / 1000
if (videoStartTime <= eventTime && eventTime <= videoEndTime) {
if(!video.details.confidence)video.details.confidence = 0
video.detections[seekPosition] = event
eventsLabeledByTime[video.mid][video.time][seekPosition] = event
}else{
newSetOfEventsWithoutChecked[n] = video
}
})
eventsToCheck = newSetOfEventsWithoutChecked
}
var prepareVideosAndEventsForTable = function(monitorId,videos,events){
var chartData = []
eventsLabeledByTime[monitorId] = {}
$.each(videos,function(n,video){
eventsLabeledByTime[monitorId][video.time] = {}
if(videos[n - 1])video.videoAfter = videos[n - 1]
if(videos[n + 1])video.videoBefore = videos[n + 1]
checkEventsAgainstVideo(video,events)
chartData.push({
group: loadedTableGroupIds[monitorId],
content: `<div timeline-video-file="${video.mid}${video.time}">
${video.time}
<div class="progress">
<div class="progress-bar progress-bar-danger" role="progressbar" style="width:0%;"><span></span></div>
</div>
</div>`,
start: video.time,
end: video.end,
videoInfo: video
})
})
$.each(events,function(n,event){
var eventReason = event.details && event.details.reason ? event.details.reason.toUpperCase() : "UNKNOWN"
var eventSlotTag = eventReason
if(eventReason === 'OBJECT' && event.details.matrices && event.details.matrices[0]){
eventSlotTag = []
event.details.matrices.forEach(function(matrix){
eventSlotTag.push(matrix.tag)
})
eventSlotTag = eventSlotTag.join(', ')
}
chartData.push({
group: loadedTableGroupIds[monitorId + '_events'],
content: `<div timeline-event="${event.time}">${eventSlotTag}</div>`,
start: event.time,
eventInfo: event
})
})
return chartData
}
var getMiniEventsChartConfig = function(video){
var monitorId = video.mid
var labels = []
var chartData = []
var events = video.detections
$.each(events,function(n,v){
if(!v.details.confidence){v.details.confidence=0}
var time = moment(v.time).format('MM/DD/YYYY HH:mm:ss')
labels.push(time)
chartData.push(v.details.confidence)
})
var timeFormat = 'MM/DD/YYYY HH:mm:ss';
Chart.defaults.global.defaultFontColor = '#fff';
var config = {
type: 'bar',
data: {
labels: labels,
datasets: [{
type: 'line',
label: 'Motion Confidence',
backgroundColor: window.chartColors.blue,
borderColor: window.chartColors.red,
data: chartData,
}]
},
options: {
maintainAspectRatio: false,
title: {
fontColor: "white",
text:"Events in this video"
},
scales: {
xAxes: [{
type: "time",
display: true,
time: {
format: timeFormat,
}
}],
},
}
};
return config
}
var drawMiniEventsChart = function(video,chartConfig){
var videoContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${video.mid}]`)
var canvas = videoContainer.find('canvas')
var ctx = canvas[0].getContext("2d")
var miniChart = new Chart(ctx, chartConfig)
canvas.click(function(f) {
var target = miniChart.getElementsAtEvent(f)[0];
if(!target){return false}
var event = video.detections[target._index]
var video1 = videoContainer.find('video')[0]
video1.currentTime = moment(event.time).diff(moment(video.time),'seconds')
video1.play()
})
}
var getAllChartDataForLoadedVideos = function(){
var chartData = []
Object.keys(powerVideoLoadedVideos).forEach(function(monitorId,n){
var videos = powerVideoLoadedVideos[monitorId]
var events = powerVideoLoadedEvents[monitorId]
var parsedVideos = prepareVideosAndEventsForTable(monitorId,videos,events)
powerVideoLoadedChartData[monitorId] = parsedVideos
chartData = chartData.concat(parsedVideos)
})
return chartData
}
var visuallySelectItemInRow = function(video){
powerVideoTimelineStripsContainer.find(`[timeline-video-file="${video.mid}${video.time}"]`).parents('.vis-item').addClass('vis-selected')
}
var visuallyDeselectItemInRow = function(video){
powerVideoTimelineStripsContainer.find(`[timeline-video-file="${video.mid}${video.time}"]`).parents('.vis-item').removeClass('vis-selected')
}
var drawTableTimeout = null
var drawLoadedTableData = function(){
// destroy old
try{
if(activeTimeline && activeTimeline.destroy){
activeTimeline.destroy()
}
}catch(err){
}
//
powerVideoTimelineStripsContainer.html(`<div class="loading"><i class="fa fa-spinner fa-pulse"></i><div class="epic-text">${lang['Please Wait...']}</div></div>`)
clearTimeout(drawTableTimeout)
drawTableTimeout = setTimeout(function(){
var container = powerVideoTimelineStripsContainer[0]
var groupsDataSet = new vis.DataSet()
var groups = []
var groupId = 1
Object.keys(powerVideoLoadedVideos).forEach(function(monitorId,n){
groups.push({
id: groupId,
content: monitorId
})
groupId += 1
groups.push({
id: groupId,
content: lang.Events
})
groupId += 1
loadedTableGroupIds[monitorId] = groupId - 2
loadedTableGroupIds[monitorId + '_events'] = groupId - 1
})
groupsDataSet.add(groups)
var chartData = getAllChartDataForLoadedVideos()
if(chartData.length > 0){
var items = new vis.DataSet(chartData)
var options = {
selectable: false,
stack: false,
showCurrentTime: false,
}
// Create a Timeline
var timeline = new vis.Timeline(container, items, groupsDataSet, options)
powerVideoTimelineStripsContainer.find('.loading').remove()
var timeChanging = false
timeline.on('rangechange', function(properties){
timeChanging = true
})
timeline.on('rangechanged', function(properties){
setTimeout(function(){
timeChanging = false
},300)
})
timeline.on('click', function(properties){
if(!timeChanging){
var selectedTime = properties.time
var videosAtSameTime = findAllVideosAtTime(selectedTime)
powerVideoTimelineStripsContainer.find('.vis-item').removeClass('vis-selected')
$.each(videosAtSameTime,function(monitorId,videos){
var selectedVideo = videos[0]
if(selectedVideo){
loadVideoIntoMonitorSlot(selectedVideo,selectedTime)
visuallySelectItemInRow(selectedVideo)
}
})
}
})
activeTimeline = timeline
}else{
powerVideoTimelineStripsContainer.html(`<div class="loading"><i class="fa fa-exclamation-circle"></i><div class="epic-text">${lang['No Data']}</div></div>`)
}
},1000)
}
var drawMatrices = function(event,options){
var streamObjectsContainer = options.streamObjectsContainer
var height = options.height
var width = options.width
var monitorId = options.mid
var widthRatio = width / event.details.imgWidth
var heightRatio = height / event.details.imgHeight
streamObjectsContainer.find('.stream-detected-object[name="'+event.details.name+'"]').remove()
var html = ''
$.each(event.details.matrices,function(n,matrix){
html += `<div class="stream-detected-object" name="${event.details.name}" style="height:${heightRatio * matrix.height}px;width:${widthRatio * matrix.width}px;top:${heightRatio * matrix.y}px;left:${widthRatio * matrix.x}px;">`
if(matrix.tag)html += `<span class="tag">${matrix.tag}</span>`
html += '</div>'
})
streamObjectsContainer.append(html)
}
var attachEventsToVideoActiveElement = function(video){
var monitorId = video.mid
var videoPlayerContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${monitorId}]`)
var videoElement = videoPlayerContainer.find(`video.videoNow`)
var streamObjectsContainer = videoPlayerContainer.find(`.videoPlayer-stream-objects`)
var detectionInfoContainerMotion = videoPlayerContainer.find(`.videoPlayer-detection-info-motion`)
var detectionInfoContainerObject = videoPlayerContainer.find(`.videoPlayer-detection-info-object`)
var detectionInfoContainerRaw = videoPlayerContainer.find(`.videoPlayer-detection-info-raw`)
var motionMeterProgressBar = videoPlayerContainer.find(`.videoPlayer-motion-meter .progress-bar`)
var motionMeterProgressBarTextBox = videoPlayerContainer.find(`.videoPlayer-motion-meter .progress-bar span`)
var videoCurrentTimeProgressBar = powerVideoTimelineStripsContainer.find(`[timeline-video-file="${video.mid}${video.time}"] .progress-bar`)[0]
var preloadedNext = false
var reinitializeStreamObjectsContainer = function(){
height = videoElement.height()
width = videoElement.width()
}
reinitializeStreamObjectsContainer()
$(videoElement)
.resize(reinitializeStreamObjectsContainer)
// .off('loadeddata').on('loadeddata', function() {
// reinitializeStreamObjectsContainer()
// var allLoaded = true
// getAllActiveVideosInSlots().each(function(n,videoElement){
// if(!videoElement.readyState === 4)allLoaded = false
// })
// setTimeout(function(){
// if(allLoaded){
// playAllSlots()
// }
// },1500)
// })
// .off("pause").on("pause",function(){
// console.log(monitorId,'pause')
// })
// .off("play").on("play",function(){
// console.log(monitorId,'play')
// })
.off("timeupdate").on("timeupdate",function(){
var event = eventsLabeledByTime[monitorId][video.time][parseInt(this.currentTime)]
if(event){
if(event.details.matrices){
drawMatrices(event,{
streamObjectsContainer: streamObjectsContainer,
monitorId: monitorId,
height: height,
width: width,
})
detectionInfoContainerObject.html(jsonToHtmlBlock(event.details.matrices))
}
if(event.details.confidence){
motionMeterProgressBar.css('width',event.details.confidence+'%')
motionMeterProgressBarTextBox.text(event.details.confidence)
var html = `<div>${lang['Region']} : ${event.details.name}</div>
<div>${lang['Confidence']} : ${event.details.confidence}</div>
<div>${lang['Plugin']} : ${event.details.plug}</div>`
detectionInfoContainerMotion.html(html)
// detectionInfoContainerRaw.html(jsonToHtmlBlock({`${lang['Plug']}`:event.details.plug}))
}
}
var currentTime = this.currentTime;
var watchPoint = Math.floor((currentTime/this.duration) * 100)
if(!preloadedNext && watchPoint >= 75){
preloadedNext = true
var videoAfter = videoPlayerContainer.find(`video.videoAfter`)[0]
videoAfter.setAttribute('preload',true)
}
if(videoCurrentTimeProgressBar)videoCurrentTimeProgressBar.style.width = `${watchPoint}%`
extenders.onVideoPlayerTimeUpdateExtensions.forEach(function(extender){
extender(videoElement,watchPoint)
})
})
var onEnded = function() {
visuallyDeselectItemInRow(video)
if(video.videoAfter){
visuallySelectItemInRow(video.videoAfter)
loadVideoIntoMonitorSlot(video.videoAfter)
}
}
videoElement[0].onended = onEnded
videoElement[0].onerror = onEnded
}
var dettachEventsToVideoActiveElement = function(monitorId){
var videoElement = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${monitorId}] video.videoNow`)
$(videoElement)
// .off('loadeddata')
.off("pause")
.off("play")
.off("timeupdate")
}
var findAllVideosAtTime = function(selectedTime){
var time = new Date(selectedTime)
var parsedVideos = {}
$.each(powerVideoLoadedVideos,function(monitorId,videos){
var videosFilteredByTime = videos.filter(function(video){
return (
(new Date(video.time)) <= time && time < (new Date(video.end))
)
});
parsedVideos[monitorId] = videosFilteredByTime
})
return parsedVideos
}
var resetVisualDetectionDataForMonitorSlot = function(monitorId){
var videoPlayerContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${monitorId}]`)
var streamObjectsContainer = videoPlayerContainer.find(`.videoPlayer-stream-objects`)
var detectionInfoContainerObject = videoPlayerContainer.find(`.videoPlayer-detection-info-object`)
var detectionInfoContainerMotion = videoPlayerContainer.find(`.videoPlayer-detection-info-motion`)
var motionMeterProgressBar = videoPlayerContainer.find(`.videoPlayer-motion-meter .progress-bar`)
var motionMeterProgressBarTextBox = videoPlayerContainer.find(`.videoPlayer-motion-meter .progress-bar span`)
detectionInfoContainerObject.empty()
detectionInfoContainerMotion.empty()
streamObjectsContainer.empty()
motionMeterProgressBar.css('width','0')
motionMeterProgressBarTextBox.text('0')
}
var loadVideoIntoMonitorSlot = function(video,selectedTime){
if(!video)return
resetVisualDetectionDataForMonitorSlot(video.mid)
currentlyPlayingVideos[video.mid] = video
var timeToStartAt = selectedTime - new Date(video.time)
var numberOfMonitors = Object.keys(powerVideoLoadedVideos).length
// if(numberOfMonitors > 3)numberOfMonitors = 3 //start new row after 3
if(numberOfMonitors == 1)numberOfMonitors = 2 //make single monitor not look like a doofus
if(timeToStartAt < 0)timeToStartAt = 0
var widthOfBlock = 100 / numberOfMonitors
var videoContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${video.mid}] .videoPlayer-buffers`)
if(videoContainer.length === 0){
if(!monitorSlotPlaySpeeds)monitorSlotPlaySpeeds[video.mid] = {}
powerVideoMonitorViewsElement.append(`<div class="videoPlayer" style="width:${widthOfBlock}%" data-mid="${video.mid}">
<div class="videoPlayer-detection-info">
<canvas style="height:400px"></canvas>
</div>
<div class="videoPlayer-stream-objects"></div>
<div class="videoPlayer-buffers"></div>
<div class="videoPlayer-motion-meter progress" title="${lang['Motion Meter']}">
<div class="progress-bar progress-bar-danger" role="progressbar" style="width:0%;"><span></span></div>
</div>
</div>`)
videoContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${video.mid}] .videoPlayer-buffers`)
}else{
powerVideoMonitorViewsElement.find('.videoPlayer').css('width',`${widthOfBlock}%`)
}
var videoCurrentNow = videoContainer.find('.videoNow')
var videoCurrentAfter = videoContainer.find('.videoAfter')
// var videoCurrentBefore = videoContainer.find('.videoBefore')
dettachEventsToVideoActiveElement(video.mid)
videoContainer.find('video').each(function(n,v){
v.pause()
})
var videoIsSame = (video.href == videoCurrentNow.attr('video'))
var videoIsAfter = (video.href == videoCurrentAfter.attr('video'))
// var videoIsBefore = (video.href == videoCurrentBefore.attr('video'))
var drawVideoHTML = function(position){
var videoData
var exisitingElement = videoContainer.find('.' + position)
if(position){
videoData = video[position]
}else{
position = 'videoNow'
videoData = video
}
if(videoData){
videoContainer.append('<video class="video_video '+position+'" video="'+videoData.href+'" playsinline><source src="'+videoData.href+'" type="video/'+videoData.ext+'"></video>')
}
}
if(
videoIsSame ||
videoIsAfter
// || videoIsBefore
){
switch(true){
case videoIsSame:
var videoNow = videoContainer.find('video.videoNow')[0]
if(!videoNow.paused)videoNow.pause()
videoNow.currentTime = timeToStartAt / 1000
if(videoNow.paused)videoNow.play()
return
break;
case videoIsAfter:
// videoCurrentBefore.remove()
videoCurrentNow.remove()
videoCurrentAfter.removeClass('videoAfter').addClass('videoNow')
// videoCurrentNow.removeClass('videoNow').addClass('videoBefore')
drawVideoHTML('videoAfter')
break;
// case videoIsBefore:
// videoCurrentAfter.remove()
// videoCurrentBefore.removeClass('videoBefore').addClass('videoNow')
// videoCurrentNow.removeClass('videoNow').addClass('videoAfter')
// drawVideoHTML('videoBefore')
// break;
}
}else{
videoContainer.empty()
drawVideoHTML()//videoNow
// drawVideoHTML('videoBefore')
drawVideoHTML('videoAfter')
}
var videoNow = videoContainer.find('video.videoNow')[0]
attachEventsToVideoActiveElement(video)
//
videoNow.setAttribute('preload',true)
videoNow.muted = true
videoNow.playbackRate = monitorSlotPlaySpeeds[video.mid] || 1
videoNow.currentTime = timeToStartAt / 1000
videoNow.play()
extenders.onVideoPlayerCreateExtensions.forEach(function(extender){
extender(videoElement,watchPoint)
})
drawMiniEventsChart(video,getMiniEventsChartConfig(video))
}
var getSelectedMonitors = function(){
return powerVideoMonitorsListElement.find('.active')
}
var getAllActiveVideosInSlots = function(){
return powerVideoMonitorViewsElement.find('video.videoNow')
}
var pauseAllSlots = function(){
getAllActiveVideosInSlots().each(function(n,video){
if(!video.paused)video.pause()
})
}
var toggleZoomAllSlots = function(){
powerVideoMonitorViewsElement.find(`.videoPlayer`).each(function(n,videoContainer){
var streamWindow = $(videoContainer)
var monitorId = streamWindow.attr('data-mid')
var enabled = streamWindow.attr('zoomEnabled')
if(enabled === '1'){
streamWindow
.attr('zoomEnabled','0')
.off('mouseover')
.off('mouseout')
.off('mousemove')
.off('touchmove')
.find('.zoomGlass').remove()
}else{
const magnifyStream = function(e){
var videoElement = streamWindow.find('video.videoNow')
console.log(videoElement[0].currentTime)
magnifyStream({
p: streamWindow,
videoUrl: streamWindow.find('video.videoNow').find('source').attr('src'),
setTime: videoElement[0].currentTime,
monitor: loadedMonitors[monitorId],
targetForZoom: 'video.videoNow',
magnifyOffsetElement: '.videoPlayer-buffers',
zoomAmount: 1,
auto: false,
animate: false,
pageX: e.pageX,
pageY: e.pageY
},$user)
}
streamWindow
.attr('zoomEnabled','1')
.on('mouseover', function(){
streamWindow.find(".zoomGlass").show()
})
.on('mouseout', function(){
streamWindow.find(".zoomGlass").hide()
})
.on('mousemove', magnifyStream)
.on('touchmove', magnifyStream)
}
})
}
var playAllSlots = function(){
getAllActiveVideosInSlots().each(function(n,video){
if(video.paused)video.play()
})
}
var setPlaySpeedOnAllSlots = function(playSpeed){
Object.keys(powerVideoLoadedVideos).forEach(function(monitorId){
monitorSlotPlaySpeeds[monitorId] = playSpeed
})
getAllActiveVideosInSlots().each(function(n,video){
video.playbackRate = playSpeed
})
}
var nextVideoAllSlots = function(){
Object.keys(currentlyPlayingVideos).forEach(function(monitorId){
var video = currentlyPlayingVideos[monitorId]
visuallyDeselectItemInRow(video)
visuallySelectItemInRow(video.videoAfter)
loadVideoIntoMonitorSlot(video.videoAfter,0)
})
}
var previousVideoAllSlots = function(){
Object.keys(currentlyPlayingVideos).forEach(function(monitorId){
var video = currentlyPlayingVideos[monitorId]
visuallyDeselectItemInRow(video)
visuallySelectItemInRow(video.videoBefore)
loadVideoIntoMonitorSlot(video.videoBefore,0)
})
}
onWebSocketEvent(function (d){
switch(d.f){
case'videos&events':
if(tabTree.name === 'powerVideo'){
var videos = d.videos.videos
var events = d.events
loadVideosToTimeLineMemory(d.id,videos,events)
drawLoadedTableData()
}
break;
}
})
$('body')
.on('dblclick','.videoPlayer',function(){
var el = $(this)
$('.videoPlayer-detection-info').addClass('hide')
fullScreenInit(this)
})
.on('click','[data-monitor]',function(){
var el = $(this)
var monitorId = el.attr('data-monitor')
el.toggleClass('active')
if(el.hasClass('active')){
requestTableData(monitorId)
}else{
unloadTableData(monitorId)
}
})
.on('click','[powerVideo-control]',function(){
var el = $(this)
var controlType = el.attr('powerVideo-control')
switch(controlType){
case'toggleZoom':
toggleZoomAllSlots()
break;
case'playAll':
playAllSlots()
break;
case'pauseAll':
pauseAllSlots()
break;
case'playSpeedAll':
var playSpeed = el.attr('data-speed')
setPlaySpeedOnAllSlots(playSpeed)
break;
case'previousVideoAll':
playAllSlots()
previousVideoAllSlots()
break;
case'nextVideoAll':
playAllSlots()
nextVideoAllSlots()
break;
}
});
addOnTabOpen('powerVideo', function () {
drawMonitorsList()
})
// addOnTabReopen('powerVideo', function () {
// drawMonitorsList()
// })
$.powerVideoViewer = {
window: powerVideoWindow,
drawMonitorsList: drawMonitorsList,
activeTimeline: activeTimeline,
monitorListElement: powerVideoMonitorsListElement,
monitorViewsElement: powerVideoMonitorViewsElement,
timelineStripsElement: powerVideoTimelineStripsContainer,
dateRangeElement: powerVideoDateRangeElement,
loadedVideos: powerVideoLoadedVideos,
loadedEvents: powerVideoLoadedEvents,
loadedChartData: powerVideoLoadedChartData,
loadedTableGroupIds: loadedTableGroupIds,
extenders: extenders
}
})

View File

@ -19,6 +19,7 @@
'home/timelapseViewer',
'home/eventFilters',
'home/cameraProbe',
'home/powerVideo',
'home/onvifScanner',
'home/configFinder',
'home/logViewer',

View File

@ -37,11 +37,11 @@ drawBlock = function(monitorSettings){
if(monitorSettings.attribute){
attributes.push(monitorSettings.attribute)
}
if(!monitorSettings.id){
if(!monitorSettings.noId && !monitorSettings.id && monitorSettings.name){
var userSettingsId = monitorSettings.name.replace(/[^a-zA-Z ]/g, '').replace(/[^a-zA-Z ]/g, '').replace(/ /g, '')
monitorSettings.id = userSettingsId
}
attributes.push(`id="${monitorSettings.id}"`)
if(monitorSettings.id)attributes.push(`id="${monitorSettings.id}"`);
if(monitorSettings.color){
sectionClass.push(monitorSettings.color)
}
@ -97,197 +97,213 @@ drawBlock = function(monitorSettings){
drawBlock(settingsBlock)
})
}
if(monitorSettings.info){
monitorSettings.info.forEach(function(field){
let evaluation = `${field.evaluation || ''}`
if(field.ejs){
try{ %>
<%- include(`${__dirname}/${field.ejs}.ejs`) %>
<% }catch(err){
console.log(err)
if(monitorSettings.info){
function drawInfoItem(field){
let evaluation = `${field.evaluation || ''}`
if(field.ejs){
try{ %>
<%- include(`${__dirname}/${field.ejs}.ejs`) %>
<% }catch(err){
console.log(err)
}
}else if(field.isFormGroupGroup === true){
drawBlock(field)
}else{
if(field.notForSubAccount === true){
var notForSubAccount = '!details.sub'
if(!field.evaluation){
evaluation = notForSubAccount
}else{
evaluation += ' && ' + notForSubAccount
}
}else if(field.isFormGroupGroup === true){
drawBlock(field)
}else{
if(field.notForSubAccount === true){
var notForSubAccount = '!details.sub'
if(!field.evaluation){
evaluation = notForSubAccount
}else{
evaluation += ' && ' + notForSubAccount
}
}
if(evaluation && !eval(evaluation)){
return
}
var hidden = ''
if(field.hidden === true){
hidden = 'style="display:none"'
}
var fieldClass = []
var attributes = []
if(field.name && field.name.indexOf('=') > -1){
attributes.push(field.name)
}else if(field.name){
attributes.push("name=" + field.name)
}
if(field.placeholder || field.default){
attributes.push(`placeholder="${field.placeholder || field.default}"`)
}else if(field.example){
attributes.push(`placeholder="Example : ${field.example}"`)
}
if(field.default){
attributes.push(`data-default="${field.default}"`)
}
if(field.attribute){
attributes.push(field.attribute)
}
if(field.selector){
attributes.push(`selector="${field.selector}"`)
}
if(field.id){
attributes.push(`id="${field.id}"`)
}
if(field.class){
fieldClass.push(`${field.class}`)
}
var possiblities = field.possible || []
var fieldType = field.fieldType || 'text'
var fieldElement = ''
var preFill = field.preFill || ''
switch(fieldType){
case'btn-group':
let fieldBtnContent = ``
field.btns.forEach((btn) => {
let btnClass = []
let btnAttributes = []
const btnBaseElement = btn.forForm || field.forForm ? 'button' : 'a'
if(btn.class){
btnClass.push(`${btn.class}`)
}
if(btn.attribute){
btnAttributes.push(btn.attribute)
}
fieldBtnContent += `<${btnBaseElement} class="btn ${btnClass.join(' ')}" ${btnAttributes.join(' ')}>${btn.btnContent}</${btnBaseElement}>`
})
fieldElement = `<div class="btn-group btn-group-justified ${fieldClass.join(' ')}" ${attributes.join(' ')}>${fieldBtnContent}</div>`
break;
case'btn':
baseElement = field.forForm ? 'button' : 'a'
fieldElement = `<${baseElement} class="btn btn-block ${fieldClass.join(' ')}" ${attributes.join(' ')}>${field.btnContent}</${baseElement}>`
break;
case'ul':
fieldElement = `<ul ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></ul>`
break;
case'div':
fieldElement = `<div ${attributes.join(' ')} class="${fieldClass.join(' ')}" >${field.divContent || ''}</div>`
break;
case'iconCard':
fieldElement = `<div ${attributes.join(' ')} class="card mb-3 border-0 ${fieldClass.join(' ')}">
<div class="card-body">
<h6 class="card-title text-white">${field.label}</h6>
<div class="row">
<h3 class="col-md-6 text-white">${field.text}</h3>
<h3 class="col-md-6 text-right text-muted" style="opacity:0.8"><i class="fa fa-2x fa-${field.icon}"></i></h3>
</div>
</div>
</div>`
break;
case'indicatorBar':
fieldElement = `<div ${attributes.join(' ')} id="indicator-${field.name}" class="mb-2 ${fieldClass.join(' ')}">
<div class="d-flex flex-row text-white mb-1">
<div class="pr-2">
<i class="fa fa-${field.icon}"></i>
</div>
<div class="flex-grow-1">
<small>${field.label}</small>
</div>
<div>
<small class="indicator-percent ${field.indicatorPercentClass || ''}"><i class="fa fa-spinner fa-pulse"></i></small>
</div>
</div>
<div>
<div class="progress">
<div class="progress-bar progress-bar-${field.color || 'warning'}" role="progressbar" style="width: ${field.percent || '0'}%;"></div>
</div>
</div>
</div>`
break;
case'form':
fieldElement = `<form ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></form>`
break;
case'table':
fieldElement = `<table ${hidden} ${attributes.join(' ')} class="${fieldClass.join(' ')}"><tbody></tbody></table>`
break;
case'number':
if(field.numberMin){
attributes.push(`min="${field.numberMin}"`)
}
if(field.numberMax){
attributes.push(`max="${field.numberMax}"`)
}
fieldElement = `<input type="number" class="form-control ${fieldClass.join(' ')}" ` + attributes.join(' ') + '>'
break;
case'password':
fieldElement = `<input type="password" class="form-control ${fieldClass.join(' ')}" ` + attributes.join(' ') + '>'
break;
case'text':
fieldElement = `<input class="form-control ${fieldClass.join(' ')}" ${attributes.join(' ')} value="${preFill}">`
break;
case'range':
fieldElement = `<input type="range" class="form-range ${fieldClass.join(' ')}" ${attributes.join(' ')} min="${field.min}" max="${field.max}">`
break;
case'textarea':
fieldElement = `<textarea class="form-control ${fieldClass.join(' ')}" ` + attributes.join(' ') + '></textarea>'
break;
case'select':
fieldElement = `<select class="form-control ${fieldClass.join(' ')}" ${attributes.join(' ')}>`
fieldElement += buildOptions(field,possiblities)
fieldElement += '</select>'
break;
}
if(field['form-group-class-pre-pre-layer']){ %>
<div class="<%- field['form-group-class-pre-pre-layer'] %>">
<% }
if(field['form-group-class-pre-layer']){ %>
<div class="<%- field['form-group-class-pre-layer'] %>">
<% }
if(
fieldType === 'ul' ||
fieldType === 'div' ||
fieldType === 'iconCard' ||
fieldType === 'indicatorBar' ||
fieldType === 'cardBlock' ||
fieldType === 'btn' ||
fieldType === 'btn-group' ||
fieldType === 'table' ||
fieldType === 'form'
){ %>
<%- fieldElement %>
<% }else{ %>
<div <%- hidden %> class="form-group <%- field['form-group-class'] %>">
<div>
<label><%- field.field %></label>
</div>
<div class="mb-2">
<span>
<% if(field.description){ %>
<small><%- field.description %></small>
<% } %>
</span>
</div>
<div><%- fieldElement %></div>
}
if(evaluation && !eval(evaluation)){
return
}
var hidden = ''
if(field.hidden === true){
hidden = 'style="display:none"'
}
var fieldClass = []
var attributes = []
if(field.name && field.name.indexOf('=') > -1){
attributes.push(field.name)
}else if(field.name){
attributes.push("name=" + field.name)
}
if(field.placeholder || field.default){
attributes.push(`placeholder="${field.placeholder || field.default}"`)
}else if(field.example){
attributes.push(`placeholder="Example : ${field.example}"`)
}
if(field.default){
attributes.push(`data-default="${field.default}"`)
}
if(field.attribute){
attributes.push(field.attribute)
}
if(field.selector){
attributes.push(`selector="${field.selector}"`)
}
if(field.styles || field.style){
attributes.push(`style="${field.styles || field.style}"`)
}
if(field.id){
attributes.push(`id="${field.id}"`)
}
if(field.class){
fieldClass.push(`${field.class}`)
}
var possiblities = field.possible || []
var fieldType = field.fieldType || 'text'
var fieldElement = ''
var preFill = field.preFill || ''
switch(fieldType){
case'btn-group':
let fieldBtnContent = ``
field.btns.forEach((btn) => {
let btnClass = []
let btnAttributes = []
const btnBaseElement = btn.forForm || field.forForm ? 'button' : 'a'
if(btn.class){
btnClass.push(`${btn.class}`)
}
if(btn.attribute){
btnAttributes.push(btn.attribute)
}
fieldBtnContent += `<${btnBaseElement} class="btn ${btnClass.join(' ')}" ${btnAttributes.join(' ')}>${btn.btnContent}</${btnBaseElement}>`
})
fieldElement = `<div class="btn-group btn-group-justified ${fieldClass.join(' ')}" ${attributes.join(' ')}>${fieldBtnContent}</div>`
break;
case'btn':
baseElement = field.forForm ? 'button' : 'a'
fieldElement = `<${baseElement} class="btn btn-block ${fieldClass.join(' ')}" ${attributes.join(' ')}>${field.btnContent}</${baseElement}>`
break;
case'ul':
fieldElement = `<ul ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></ul>`
break;
case'div':
fieldElement = `<div ${attributes.join(' ')} class="${fieldClass.join(' ')}" >${field.divContent || ''}`
break;
case'div':
fieldElement = `${field.html}`
break;
case'iconCard':
fieldElement = `<div ${attributes.join(' ')} class="card mb-3 border-0 ${fieldClass.join(' ')}">
<div class="card-body">
<h6 class="card-title text-white">${field.label}</h6>
<div class="row">
<h3 class="col-md-6 text-white">${field.text}</h3>
<h3 class="col-md-6 text-right text-muted" style="opacity:0.8"><i class="fa fa-2x fa-${field.icon}"></i></h3>
</div>
</div>
</div>`
break;
case'indicatorBar':
fieldElement = `<div ${attributes.join(' ')} id="indicator-${field.name}" class="mb-2 ${fieldClass.join(' ')}">
<div class="d-flex flex-row text-white mb-1">
<div class="pr-2">
<i class="fa fa-${field.icon}"></i>
</div>
<div class="flex-grow-1">
<small>${field.label}</small>
</div>
<div>
<small class="indicator-percent ${field.indicatorPercentClass || ''}"><i class="fa fa-spinner fa-pulse"></i></small>
</div>
</div>
<div>
<div class="progress">
<div class="progress-bar progress-bar-${field.color || 'warning'}" role="progressbar" style="width: ${field.percent || '0'}%;"></div>
</div>
</div>
</div>`
break;
case'form':
fieldElement = `<form ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></form>`
break;
case'table':
fieldElement = `<table ${hidden} ${attributes.join(' ')} class="${fieldClass.join(' ')}"><tbody></tbody></table>`
break;
case'number':
if(field.numberMin){
attributes.push(`min="${field.numberMin}"`)
}
if(field.numberMax){
attributes.push(`max="${field.numberMax}"`)
}
fieldElement = `<input type="number" class="form-control ${fieldClass.join(' ')}" ` + attributes.join(' ') + '>'
break;
case'password':
fieldElement = `<input type="password" class="form-control ${fieldClass.join(' ')}" ` + attributes.join(' ') + '>'
break;
case'text':
fieldElement = `<input class="form-control ${fieldClass.join(' ')}" ${attributes.join(' ')} value="${preFill}">`
break;
case'range':
fieldElement = `<input type="range" class="form-range ${fieldClass.join(' ')}" ${attributes.join(' ')} min="${field.min}" max="${field.max}">`
break;
case'textarea':
fieldElement = `<textarea class="form-control ${fieldClass.join(' ')}" ` + attributes.join(' ') + '></textarea>'
break;
case'select':
fieldElement = `<select class="form-control ${fieldClass.join(' ')}" ${attributes.join(' ')}>`
fieldElement += buildOptions(field,possiblities)
fieldElement += '</select>'
break;
}
if(field['form-group-class-pre-pre-layer']){ %>
<div class="<%- field['form-group-class-pre-pre-layer'] %>">
<% }
if(field['form-group-class-pre-layer']){ %>
<div class="<%- field['form-group-class-pre-layer'] %>">
<% }
if(
fieldType === 'ul' ||
fieldType === 'div' ||
fieldType === 'html' ||
fieldType === 'iconCard' ||
fieldType === 'indicatorBar' ||
fieldType === 'cardBlock' ||
fieldType === 'btn' ||
fieldType === 'btn-group' ||
fieldType === 'table' ||
fieldType === 'form'
){ %>
<%- fieldElement %>
<%
if(fieldType === 'div'){
if(field.info){
field.info.forEach(drawInfoItem)
} %>
<%- `</div>` %>
<% }
%>
<% }else{ %>
<div <%- hidden %> class="form-group <%- field['form-group-class'] %>">
<div>
<label><%- field.field %></label>
</div>
<div class="mb-2">
<span>
<% if(field.description){ %>
<small><%- field.description %></small>
<% } %>
</span>
</div>
<div><%- fieldElement %></div>
</div>
<% }
}
if(field['form-group-class-pre-layer']){ %>
</div>
<% }
}
if(field['form-group-class-pre-layer']){ %>
</div>
<% }
if(field['form-group-class-pre-pre-layer']){ %>
</div>
<% }
})
}
<% }
if(field['form-group-class-pre-pre-layer']){ %>
</div>
<% }
}
monitorSettings.info.forEach(drawInfoItem)
}
%>
</div>
</<%- monitorSettings.isForm ? 'form' : 'div' %>>

View File

@ -0,0 +1,19 @@
<main class="page-tab pt-3" id="tab-powerVideo">
<div class="row <%- define.Theme.isDark ? `dark` : '' %>" id="powerVideo">
<%
var drawBlock
var buildOptions
%>
<%
include fieldBuilders.ejs
%>
<% Object.keys(define['Power Viewer'].blocks).forEach(function(blockKey){
var theBlock = define['Power Viewer'].blocks[blockKey]
drawBlock(theBlock)
}) %>
</div>
<link rel="stylesheet" href="<%-window.libURL%>libs/css/vis.min.css">
<link rel="stylesheet" href="<%-window.libURL%>assets/css/bs5.powerVideo.css">
<script src="<%-window.libURL%>assets/js/bs5.powerVideo.js"></script>
<script src="<%-window.libURL%>libs/js/vis.min.js" async></script>
</main>