From dd02dea6aa4cc3abd6807ad1f162e56e5dbe947b Mon Sep 17 00:00:00 2001 From: Daniel Nowak <13685818+lowlyocean@users.noreply.github.com> Date: Wed, 7 Sep 2022 08:37:34 -0400 Subject: [PATCH 1/4] Fit vertically 2x2 as expected --- motioneye/static/css/main.css | 26 ++++++++- motioneye/static/js/main.js | 106 +++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 48 deletions(-) diff --git a/motioneye/static/css/main.css b/motioneye/static/css/main.css index 0aeda576b..056a7e496 100644 --- a/motioneye/static/css/main.css +++ b/motioneye/static/css/main.css @@ -917,6 +917,22 @@ div.page-container.four-columns div.camera-frame { width: 25%; } +div.page-container.one-row div.camera-frame { + height: 100% +} + +div.page-container.two-rows div.camera-frame { + height: 50% +} + +div.page-container.three-rows div.camera-frame { + height: 33.3333% +} + +div.page-container.four-rows div.camera-frame { + height: 25% +} + div.page-container div.camera-frame.full-screen-hidden { width: 0 !important; height: 0 !important; @@ -1263,14 +1279,18 @@ div.camera-action-button.preset9:BEFORE { div.camera-container { position: relative; padding: 0; + height: 100%; } img.camera { position: relative; - width: 100%; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + height: inherit; display: block; - transition: opacity 0.2s ease; - opacity: 1; + transition: opacity .2s ease; + opacity: 1 } img.camera.error, diff --git a/motioneye/static/js/main.js b/motioneye/static/js/main.js index 4357a2331..7a1c2f418 100644 --- a/motioneye/static/js/main.js +++ b/motioneye/static/js/main.js @@ -1099,62 +1099,78 @@ function setLayoutColumns(columns) { updateLayout(); } +Array.prototype.reshape = function(rows, cols) { + var copy = []; + + for (var r = 0; r < rows; r++) { + var row = []; + for (var c = 0; c < cols; c++) { + var i = r * cols + c; + if (i < this.length) { + row.push(this[i]); + } + } + copy.push(row); + } + return copy; +}; + function updateLayout() { + if (fitFramesVertically) { + /* make sure the height of each camera * is smaller than the height of the screen * divided by the number of layout rows */ - - /* find the max height/width ratio */ - var frames = getCameraFrames(); - var maxRatio = 0; - - frames.each(function () { - var cameraId = this.id.substring(6); - var ratio = cameraFrameRatios[cameraId]; - if (ratio > maxRatio) { - maxRatio = ratio; - } - }); - - if (!maxRatio) { - return; /* no camera frames */ - } - - var pageContainer = getPageContainer(); - var windowWidth = $(window).width(); - - var columns = layoutColumns; - if (isSingleView() || fullScreenMode || windowWidth <= 1200) { - columns = 1; /* always 1 column when in full screen or mobile */ - } - + var heightOffset = 5; /* some padding */ if (!fullScreenMode && !isSingleView()) { heightOffset += 50; /* top bar */ } - + var windowHeight = $(window).height() - heightOffset; - var maxWidth = windowWidth; - - var width = windowHeight / maxRatio * columns; - if (pageContainer.hasClass('stretched') && windowWidth > 1200) { - maxWidth *= 0.6; /* opened settings panel occupies 40% of the window width */ - } - - if (width < 100) { - width = 100; /* absolute minimum width for a frame */ - } - if (width > maxWidth) { - getPageContainer().css('width', ''); - return; /* page container width already at its maximum */ - } - - getPageContainer().css('width', width); - } - else { - getPageContainer().css('width', ''); + // 2D array representing rows/cols of the layout + var cameraDimensions = getCameraFrames().map(function () { + return { height: this.img._naturalHeight, width: this.img._naturalWidth }; + }).toArray().reshape(layoutRows, layoutColumns); + + // Take max cam height for each row, add them together + var combinedHeight = cameraDimensions.map(function(row) { + return row.length ? Math.max(...row.map(function(cam) { + return cam.height; + })) : 0 + }).reduce(function(a,b) { + return a+b; + }, 0); + + // Take max cam width for each col, add them together + var combinedWidth = [...Array(layoutColumns).keys()].map(function(col) { + return cameraDimensions.map(function(row) { + return row[col]; + }) + }).map(function(col) { + return col ? Math.max(...col.map(function(cam) { + return cam ? cam.width : 0; + })) : 0 + }).reduce(function(a,b) { + return a+b; + }, 0); + + var combinedratio = combinedWidth/combinedHeight; + + getPageContainer().css('height', windowHeight); + getPageContainer().css('width', windowHeight*combinedratio); + + var cssClasses = { + 1: 'one-row', + 2: 'two-rows', + 3: 'three-rows', + 4: 'four-rows' + }; + + getPageContainer().removeClass(Object.values(cssClasses).join(' ')); + getPageContainer().addClass(cssClasses[layoutRows]); } } From f7f73d696f20a619a126f1e381fef1b8b4d7cdb4 Mon Sep 17 00:00:00 2001 From: Daniel Nowak <13685818+lowlyocean@users.noreply.github.com> Date: Wed, 7 Sep 2022 08:54:58 -0400 Subject: [PATCH 2/4] Account for tall, non-mobile window --- motioneye/static/js/main.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/motioneye/static/js/main.js b/motioneye/static/js/main.js index 7a1c2f418..533d90478 100644 --- a/motioneye/static/js/main.js +++ b/motioneye/static/js/main.js @@ -1157,10 +1157,18 @@ function updateLayout() { return a+b; }, 0); - var combinedratio = combinedWidth/combinedHeight; + var combinedRatio = combinedWidth/combinedHeight; - getPageContainer().css('height', windowHeight); - getPageContainer().css('width', windowHeight*combinedratio); + var windowWidth = $(window).width(); + var windowRatio = windowWidth/windowHeight; + if( windowRatio > combinedRatio ) { + getPageContainer().css('height', windowHeight); + getPageContainer().css('width', windowHeight*combinedRatio); + } + else { + getPageContainer().css('height', windowWidth/combinedRatio); + getPageContainer().css('width', windowWidth); + } var cssClasses = { 1: 'one-row', @@ -1475,8 +1483,8 @@ function closeSettings() { $('div.settings-top-bar').removeClass('open').addClass('closed'); if (isSingleView()) { - pageContainer.removeClass('single-cam-edit'); - $('div.header').addClass('single-cam'); + pageContainer.removeClass('single-cam-edit'); + $('div.header').addClass('single-cam'); } updateLayout(); @@ -3595,18 +3603,18 @@ function runLoginDialog(retry) { '' + '' + '' - +i18n.gettext("Uzantnomo") + '' + + +i18n.gettext("Uzantnomo") + '' + '' + '' + '' + '' - +i18n.gettext("Pasvorto") + '' + + +i18n.gettext("Pasvorto") + '' + '' + '' + '' + '' + '' - +i18n.gettext("Memoru min")+'' + + +i18n.gettext("Memoru min")+'' + '' + '' + ''); @@ -3957,12 +3965,12 @@ function runAddCameraDialog() { content.find('tr.netcam').css('display', 'table-row'); addCameraInfo.html( - i18n.gettext("Retaj kameraoj (aŭ IP-kameraoj) estas aparatoj, kiuj denaske fluas RTSP/RTMP aŭ MJPEG-filmetojn aŭ simplajn JPEG-bildojn. Konsultu la manlibron de via aparato por ekscii la ĝustan URL RTSP, RTMP, MJPEG aŭ JPEG.")); + i18n.gettext("Retaj kameraoj (aŭ IP-kameraoj) estas aparatoj, kiuj denaske fluas RTSP/RTMP aŭ MJPEG-filmetojn aŭ simplajn JPEG-bildojn. Konsultu la manlibron de via aparato por ekscii la ĝustan URL RTSP, RTMP, MJPEG aŭ JPEG.")); } else if (typeSelect.val() == 'mmal') { content.find('tr.mmal').css('display', 'table-row'); addCameraInfo.html( - i18n.gettext("Lokaj MMAL-kameraoj estas aparatoj konektitaj rekte al via motionEye-sistemo. Ĉi tiuj estas kutime kart-specifaj kameraoj.")); + i18n.gettext("Lokaj MMAL-kameraoj estas aparatoj konektitaj rekte al via motionEye-sistemo. Ĉi tiuj estas kutime kart-specifaj kameraoj.")); } else if (typeSelect.val() == 'mjpeg') { usernameEntry.removeAttr('readonly'); @@ -3977,7 +3985,7 @@ function runAddCameraDialog() { content.find('tr.mjpeg').css('display', 'table-row'); addCameraInfo.html( - i18n.gettext("Aldonante vian aparaton kiel simplan MJPEG-kameraon anstataŭ kiel retan kameraon plibonigos la fotografaĵon, sed neniu moviĝo-detekto, bilda kaptado aŭ registrado de filmoj estos disponebla por ĝi. La kamerao devas esti alirebla por via servilo kaj via retumilo. Ĉi tiu tipo de kamerao ne kongruas kun Internet Explorer.")); + i18n.gettext("Aldonante vian aparaton kiel simplan MJPEG-kameraon anstataŭ kiel retan kameraon plibonigos la fotografaĵon, sed neniu moviĝo-detekto, bilda kaptado aŭ registrado de filmoj estos disponebla por ĝi. La kamerao devas esti alirebla por via servilo kaj via retumilo. Ĉi tiu tipo de kamerao ne kongruas kun Internet Explorer.")); } else { /* assuming v4l2 */ content.find('tr.v4l2').css('display', 'table-row'); From 6b3cb2b57cb7e116be77b39905dabd5631c2e36b Mon Sep 17 00:00:00 2001 From: Daniel Nowak <13685818+lowlyocean@users.noreply.github.com> Date: Wed, 7 Sep 2022 09:11:09 -0400 Subject: [PATCH 3/4] Account for settings panel --- motioneye/static/js/main.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/motioneye/static/js/main.js b/motioneye/static/js/main.js index 533d90478..52779582e 100644 --- a/motioneye/static/js/main.js +++ b/motioneye/static/js/main.js @@ -1160,6 +1160,9 @@ function updateLayout() { var combinedRatio = combinedWidth/combinedHeight; var windowWidth = $(window).width(); + if (getPageContainer().hasClass('stretched') && windowWidth > 1200) { + windowWidth *= 0.6; /* opened settings panel occupies 40% of the window width */ + } var windowRatio = windowWidth/windowHeight; if( windowRatio > combinedRatio ) { getPageContainer().css('height', windowHeight); From 264c09786e7260b75cf0272f30a08ad27727d7cc Mon Sep 17 00:00:00 2001 From: Daniel Nowak <13685818+lowlyocean@users.noreply.github.com> Date: Mon, 19 Sep 2022 18:03:01 -0400 Subject: [PATCH 4/4] Account for single-cam view & revert unrelated whitespace changes --- motioneye/static/css/main.css | 2 +- motioneye/static/js/main.js | 89 +++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 37 deletions(-) diff --git a/motioneye/static/css/main.css b/motioneye/static/css/main.css index 056a7e496..271e5d090 100644 --- a/motioneye/static/css/main.css +++ b/motioneye/static/css/main.css @@ -950,7 +950,7 @@ div.page-container div.camera-frame.single-cam-hidden { div.page-container div.camera-frame.single-cam { width: 100%; - height: auto; + height: inherit; transition: all 0.4s ease; /* prevents unwanted camera frame line wrapping */ } diff --git a/motioneye/static/js/main.js b/motioneye/static/js/main.js index 52779582e..5359f0b96 100644 --- a/motioneye/static/js/main.js +++ b/motioneye/static/js/main.js @@ -178,6 +178,21 @@ Array.prototype.sortKey = function (keyFunc, reverse) { }); }; +Array.prototype.reshape = function(rows, cols) { + var copy = []; + + for (var r = 0; r < rows; r++) { + var row = []; + for (var c = 0; c < cols; c++) { + var i = r * cols + c; + if (i < this.length) { + row.push(this[i]); + } + } + copy.push(row); + } + return copy; + }; /* String utilities */ @@ -1099,41 +1114,42 @@ function setLayoutColumns(columns) { updateLayout(); } -Array.prototype.reshape = function(rows, cols) { - var copy = []; - - for (var r = 0; r < rows; r++) { - var row = []; - for (var c = 0; c < cols; c++) { - var i = r * cols + c; - if (i < this.length) { - row.push(this[i]); - } - } - copy.push(row); - } - return copy; -}; - function updateLayout() { - if (fitFramesVertically) { - + + var columns = layoutColumns, rows = layoutRows; + + if (windowWidth <= 1200) { + columns = 1; /* always 1 column when in full screen or mobile */ + } + + if (isSingleView() || fullScreenMode) { + columns = 1; + rows = 1; /* single camera or fullscreen? ignore specified columns & rows */ + } + /* make sure the height of each camera * is smaller than the height of the screen * divided by the number of layout rows */ - + var heightOffset = 5; /* some padding */ if (!fullScreenMode && !isSingleView()) { heightOffset += 50; /* top bar */ } - + var windowHeight = $(window).height() - heightOffset; // 2D array representing rows/cols of the layout - var cameraDimensions = getCameraFrames().map(function () { - return { height: this.img._naturalHeight, width: this.img._naturalWidth }; - }).toArray().reshape(layoutRows, layoutColumns); + var cameraDimensions; + if( isSingleView() ) { + const singCameraFrame = getCameraFrame(singleViewCameraId)[0]; + var cameraDimensions = [ [ {height: singCameraFrame.img._naturalHeight, width: singCameraFrame.img._naturalWidth } ] ]; + } + else { + var cameraDimensions = getCameraFrames().map(function () { + return { height: this.img._naturalHeight, width: this.img._naturalWidth }; + }).toArray().reshape(rows, columns); + } // Take max cam height for each row, add them together var combinedHeight = cameraDimensions.map(function(row) { @@ -1145,7 +1161,7 @@ function updateLayout() { }, 0); // Take max cam width for each col, add them together - var combinedWidth = [...Array(layoutColumns).keys()].map(function(col) { + var combinedWidth = [...Array(columns).keys()].map(function(col) { return cameraDimensions.map(function(row) { return row[col]; }) @@ -1158,11 +1174,12 @@ function updateLayout() { }, 0); var combinedRatio = combinedWidth/combinedHeight; - + var windowWidth = $(window).width(); if (getPageContainer().hasClass('stretched') && windowWidth > 1200) { windowWidth *= 0.6; /* opened settings panel occupies 40% of the window width */ } + var windowRatio = windowWidth/windowHeight; if( windowRatio > combinedRatio ) { getPageContainer().css('height', windowHeight); @@ -1172,16 +1189,16 @@ function updateLayout() { getPageContainer().css('height', windowWidth/combinedRatio); getPageContainer().css('width', windowWidth); } - + var cssClasses = { 1: 'one-row', 2: 'two-rows', 3: 'three-rows', 4: 'four-rows' }; - + getPageContainer().removeClass(Object.values(cssClasses).join(' ')); - getPageContainer().addClass(cssClasses[layoutRows]); + getPageContainer().addClass(cssClasses[rows]); } } @@ -1486,8 +1503,8 @@ function closeSettings() { $('div.settings-top-bar').removeClass('open').addClass('closed'); if (isSingleView()) { - pageContainer.removeClass('single-cam-edit'); - $('div.header').addClass('single-cam'); + pageContainer.removeClass('single-cam-edit'); + $('div.header').addClass('single-cam'); } updateLayout(); @@ -3606,18 +3623,18 @@ function runLoginDialog(retry) { '' + '' + '' - +i18n.gettext("Uzantnomo") + '' + + +i18n.gettext("Uzantnomo") + '' + '' + '' + '' + '' - +i18n.gettext("Pasvorto") + '' + + +i18n.gettext("Pasvorto") + '' + '' + '' + '' + '' + '' - +i18n.gettext("Memoru min")+'' + + +i18n.gettext("Memoru min")+'' + '' + '' + ''); @@ -3968,12 +3985,12 @@ function runAddCameraDialog() { content.find('tr.netcam').css('display', 'table-row'); addCameraInfo.html( - i18n.gettext("Retaj kameraoj (aŭ IP-kameraoj) estas aparatoj, kiuj denaske fluas RTSP/RTMP aŭ MJPEG-filmetojn aŭ simplajn JPEG-bildojn. Konsultu la manlibron de via aparato por ekscii la ĝustan URL RTSP, RTMP, MJPEG aŭ JPEG.")); + i18n.gettext("Retaj kameraoj (aŭ IP-kameraoj) estas aparatoj, kiuj denaske fluas RTSP/RTMP aŭ MJPEG-filmetojn aŭ simplajn JPEG-bildojn. Konsultu la manlibron de via aparato por ekscii la ĝustan URL RTSP, RTMP, MJPEG aŭ JPEG.")); } else if (typeSelect.val() == 'mmal') { content.find('tr.mmal').css('display', 'table-row'); addCameraInfo.html( - i18n.gettext("Lokaj MMAL-kameraoj estas aparatoj konektitaj rekte al via motionEye-sistemo. Ĉi tiuj estas kutime kart-specifaj kameraoj.")); + i18n.gettext("Lokaj MMAL-kameraoj estas aparatoj konektitaj rekte al via motionEye-sistemo. Ĉi tiuj estas kutime kart-specifaj kameraoj.")); } else if (typeSelect.val() == 'mjpeg') { usernameEntry.removeAttr('readonly'); @@ -3988,7 +4005,7 @@ function runAddCameraDialog() { content.find('tr.mjpeg').css('display', 'table-row'); addCameraInfo.html( - i18n.gettext("Aldonante vian aparaton kiel simplan MJPEG-kameraon anstataŭ kiel retan kameraon plibonigos la fotografaĵon, sed neniu moviĝo-detekto, bilda kaptado aŭ registrado de filmoj estos disponebla por ĝi. La kamerao devas esti alirebla por via servilo kaj via retumilo. Ĉi tiu tipo de kamerao ne kongruas kun Internet Explorer.")); + i18n.gettext("Aldonante vian aparaton kiel simplan MJPEG-kameraon anstataŭ kiel retan kameraon plibonigos la fotografaĵon, sed neniu moviĝo-detekto, bilda kaptado aŭ registrado de filmoj estos disponebla por ĝi. La kamerao devas esti alirebla por via servilo kaj via retumilo. Ĉi tiu tipo de kamerao ne kongruas kun Internet Explorer.")); } else { /* assuming v4l2 */ content.find('tr.v4l2').css('display', 'table-row');