improve mobile-friendliness (#68)

nav div changes:
* make it togglable (on all devices) by hamburger button
* on narrow devices, make it closed by default and
  be at the top rather than on the left

open zoomed by default

trim some arguably less important columns on narrow displays,
and reduce some horizontal padding

always show videos full-screen on narrow displays
This commit is contained in:
Scott Lamb 2020-05-04 23:06:12 -07:00
parent be479a1ffe
commit e177cbd042
5 changed files with 81 additions and 31 deletions

View File

@ -143,9 +143,14 @@ function onSelectVideo(nvrSettingsView, camera, streamType, range, recording) {
);
const videoTitle =
camera.shortName + ', ' + formattedStart + ' to ' + formattedEnd;
const maxWidth = window.innerWidth / 2;
let width = recording.videoSampleEntryWidth;
while (width > maxWidth) {
width /= 2;
}
new VideoDialogView()
.attach($('body'))
.play(videoTitle, recording.videoSampleEntryWidth / 4, url);
.play(videoTitle, width, url);
}
/**
@ -220,7 +225,7 @@ function updateSession(session) {
return;
}
sessionBar.append($('<span id="session-username" />').text(session.username));
const logout = $('<a>logout</a>');
const logout = $('<a id="logout">logout</a>');
logout.click(() => {
api
.logout(session.csrf)
@ -377,6 +382,11 @@ export default class NVRApplication {
* Start the application.
*/
start() {
let nav = $('#nav');
$('#toggle-nav').click(() => {
nav.toggle('slide');
});
loginDialog = $('#login').dialog({
autoOpen: false,
modal: true,

View File

@ -1,12 +1,24 @@
body {
font-family: Arial, Helvetica, sans-serif;
}
#top {
width: 100%;
padding-bottom: 2ex;
}
#toggle-nav {
font-size: 1.25em;
cursor: pointer;
}
#nav {
float: left;
margin: 0 0.5em 0.5ex 0;
}
#session {
float: right;
}
#logout {
cursor: pointer;
}
#datetime .ui-datepicker {
width: 100%;
@ -14,17 +26,15 @@ body {
#videos {
display: inline-block;
padding-top: 0.5em;
padding-left: 1em;
padding-right: 1em;
}
#videos tbody:after {
content: "";
display: block;
height: 3ex;
}
table.videos {
table#videos {
border-collapse: collapse;
/*border-spacing: 0.5em 0.5ex;*/
}
tbody tr.name {
font-size: 110%;
@ -47,8 +57,8 @@ tr.r td {
tr.r th,
tr.r td {
border: 0;
padding: 0.5ex 1.5em;
text-align: right;
padding: 0.5ex 1.5em;
}
fieldset {
@ -62,9 +72,6 @@ fieldset legend {
#from, #to {
padding-right: 0.75em;
}
#st {
width: 100%;
}
#range {
padding: 0.5em 0;
@ -78,3 +85,17 @@ video {
width: 100%;
height: 100%;
}
@media only screen and (max-width: 768px) {
#nav {
float: none;
display: none;
}
.resolution, .frameRate, .size {
display: none;
}
tr.r th,
tr.r td {
padding: 0.5ex 0.5em;
}
}

View File

@ -3,9 +3,12 @@
<html lang="en">
<head>
<title>Moonfire NVR</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="session">
<div id="top">
<a id="toggle-nav">&#x2630;</a>
<span id="session"></div>
</div>
<div id="nav">
<form action="#">
@ -16,14 +19,15 @@
<fieldset id="datetime">
<legend>Date &amp; Time Range</legend>
<div id="from">
<div id="start-date"></div>
<div id="st">
<label for="start-time">Time:</label>
<input id="start-time" name="start-time" type="text" title="Starting
time within the day. Blank for the beginning of the day. Otherwise
HH:mm[:ss[:FFFFF]][+-HH:mm], where F is 90,000ths of a second.
Timezone is normally left out; it's useful once a year during the
ambiguous times of the &quot;fall back&quot; hour."></div>
<div id="start-date"></div>
<label for="start-time">Time:</label>
<input id="start-time" name="start-time" type="text"
max-length="20"
title="Starting time within the day. Blank for the beginning of
the day. Otherwise HH:mm[:ss[:FFFFF]][+-HH:mm], where F is
90,000ths of a second. Timezone is normally left out; it's useful
once a year during the ambiguous times of the &quot;fall
back&quot; hour.">
</div>
<div id="range">Range:
<input type="radio" name="end-date-type" id="end-date-same" checked>
@ -33,12 +37,12 @@
</div>
<div id="to">
<div id="end-date"></div>
<label for="start-time">Time:</label>
<input id="end-time" name="end-time" type="text" title="Ending
time within the day. Blank for the end of the day. Otherwise
HH:mm[:ss[:FFFFF]][+-HH:mm], where F is 90,000ths of a second.
Timezone is normally left out; it's useful once a year during the
ambiguous times of the &quot;fall back&quot; hour.">
<label for="end-time">Time:</label>
<input id="end-time" name="end-time" type="text" max-length="20"
title="Ending time within the day. Blank for the end of the day.
Otherwise HH:mm[:ss[:FFFFF]][+-HH:mm], where F is 90,000ths of a
second. Timezone is normally left out; it's useful once a year
during the ambiguous times of the &quot;fall back&quot; hour.">
</div>
</fieldset>
<fieldset>

View File

@ -112,7 +112,7 @@ export default class RecordingsView {
$('<tr class="hdr">').append(
$(
_columnOrder
.map((name) => '<th>' + _columnLabels[name] + '</th>')
.map((name) => `<th class="${name}">${_columnLabels[name]}</th>`)
.join('')
)
),
@ -271,7 +271,7 @@ export default class RecordingsView {
$('tr.r', tbody).remove();
this.recordings_.forEach((r) => {
const row = $('<tr class="r" />');
row.append(_columnOrder.map(() => $('<td/>')));
row.append(_columnOrder.map((c) => $(`<td class="${c}"/>`)));
row.on('click', () => {
console.log('Video clicked');
if (this.clickHandler_ !== null) {

View File

@ -52,7 +52,7 @@ export default class VideoDialogView {
* This does not attach the player to the DOM anywhere! In fact, construction
* of the necessary video element is delayed until an attach is requested.
* Since the close of the video removes all traces of it in the DOM, this
* apprach allows repeated use by calling attach again!
* approach allows repeated use by calling attach again!
*/
constructor() {}
@ -80,13 +80,13 @@ export default class VideoDialogView {
* @return {VideoDialogView} Returns "this" for chaining.
*/
play(title, width, url) {
const videoDomElement = this.videoElement_[0];
this.dialogElement_.dialog({
title: title,
width: width,
close: () => {
const videoDOMElement = this.videoElement_[0];
videoDOMElement.pause();
videoDOMElement.src = ''; // Remove current source to stop loading
videoDomElement.pause();
videoDomElement.src = ''; // Remove current source to stop loading
this.videoElement_ = null;
this.dialogElement_.remove();
this.dialogElement_ = null;
@ -95,6 +95,21 @@ export default class VideoDialogView {
// Now that dialog is up, set the src so video starts
console.log('Video url: ' + url);
this.videoElement_.attr('src', url);
// On narrow displays (as defined by index.css), play videos in
// full-screen mode. When the user exits full-screen mode, close the
// dialog.
const narrowWindow = $('#nav').css('float') == 'none';
if (narrowWindow) {
console.log('Narrow window; starting video in full-screen mode.');
videoDomElement.requestFullscreen();
videoDomElement.addEventListener('fullscreenchange', (event) => {
if (document.fullscreenElement !== videoDomElement) {
console.log('Closing video because user exited full-screen mode.');
this.dialogElement_.dialog("close");
}
});
}
return this;
}
}