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
parent
414f27e9ee
commit
23cbb76416
|
|
@ -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>`,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1241,5 +1241,6 @@
|
|||
"Motion Threshold":"Motion Threshold",
|
||||
"Attach Snapshot": "Attach Snapshot",
|
||||
"Invalid Settings": "Invalid Settings",
|
||||
"Detection": "Detection"
|
||||
"Detection": "Detection",
|
||||
"Cloud": "Cloud"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
})
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
'home/timelapseViewer',
|
||||
'home/eventFilters',
|
||||
'home/cameraProbe',
|
||||
'home/powerVideo',
|
||||
'home/onvifScanner',
|
||||
'home/configFinder',
|
||||
'home/logViewer',
|
||||
|
|
|
|||
|
|
@ -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' %>>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue