Merge branch 'new-schema'

The Rust portions of the merge are straightforward, but the Javascript
is not. The new-schema branch is based on my hacky prototype UI; the
master branch is based on Dolf's rewrite. I attempted to match the
new-schema changes in Dolf's new structure.
This commit is contained in:
Scott Lamb
2018-04-27 06:42:39 -07:00
67 changed files with 8771 additions and 4588 deletions

View File

@@ -38,7 +38,7 @@ import TimeStamp90kFormatter from '../support/TimeStamp90kFormatter';
import Time90kParser from '../support/Time90kParser';
/**
* Find the earliest and latest dates from an array of CameraView
* Find the earliest and latest dates from an array of StreamView
* objects.
*
* Each camera view has a "days" property, whose keys identify days with
@@ -47,20 +47,20 @@ import Time90kParser from '../support/Time90kParser';
*
* "days" for camera views that are not enabled are ignored.
*
* @param {[Iterable]} cameraViews Camera views to look into
* @param {[Iterable]} streamViews Camera views to look into
* @return {[Set, String, String]} Array with set of all dates, and
* earliest and latest dates
*/
function minMaxDates(cameraViews) {
function minMaxDates(streamViews) {
/*
* Produce a set with all dates, across all enabled cameras, that
* have at least one recording available (allDates).
*/
const allDates = new Set(
[].concat(
...cameraViews
...streamViews
.filter((v) => v.enabled)
.map((v) => Array.from(v.camera.days.keys()))
.map((v) => Array.from(v.stream.days.keys()))
)
);
return [
@@ -137,7 +137,7 @@ export default class CalendarView {
this._minDateStr = null;
this._maxDateStr = null;
this._singleDateStr = null;
this._cameraViews = null;
this._streamViews = null;
this._rangeChangedHandler = null;
}
@@ -329,12 +329,12 @@ export default class CalendarView {
* This is necessary as the camera views ultimately define the limits on
* the date pickers.
*
* @param {Iterable} cameraViews Collection of camera views
* @param {Iterable} streamViews Collection of camera views
*/
initializeWith(cameraViews) {
this._cameraViews = cameraViews;
initializeWith(streamViews) {
this._streamViews = streamViews;
[this._availableDates, this._minDateStr, this._maxDateStr] = minMaxDates(
cameraViews
streamViews
);
this._configureDatePickers();

View File

@@ -1,113 +0,0 @@
// vim: set et sw=2 ts=2:
//
// This file is part of Moonfire NVR, a security camera digital video recorder.
// Copyright (C) 2018 Dolf Starreveld <dolf@starreveld.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import $ from 'jquery';
/**
* Class to handle a group of (related) checkboxes.
*
* Each checkbox is managed through a simple object containing properties:
* - id: {String} Id (some unique value within the group)
* - selector: {String} jQuery compatible selector to find the dom element
* - checked: {Boolean} Value for checkbox
* - jq: {jQuery} jQuery element for the checkbox, or null if not found
*
* A handler can be called if a checbox changes value.
*/
export default class CheckboxGroupView {
/**
* Construct the seteup for the checkboxes.
*
* The passed group array should contain individual maps describing each
* checkbox. THe maps should contain:
* - id
* - selector: optional. If not provided #id will be used
* - checked: Initial value for checkbox, default true
* - text: Text for the checkbox label (not generated if empty)
*
* @param {Array} group Array of maps, one for each checkbox
* @param {jQuery} parent jQuery parent element to append to
*/
constructor(group = [], parent = null) {
this._group = group.slice(); // Copy
this._group.forEach((element) => {
// If parent specified, create and append
if (parent) {
let cb = `<input type="checkbox" id="${element.id}" name="${
element.id
}">`;
if (element.text) {
cb += `<label for="${element.id}">${element.text}</label>`;
}
parent.append($(cb + '<br/>'));
}
const jq = $(element.selector || `#${element.id}`);
element.jq = jq;
if (jq !== null) {
jq.prop('checked', element.checked || true);
jq.change((e) => {
if (this._checkChangeHandler) {
element.checked = e.target.checked;
this._checkChangeHandler(element);
}
});
}
});
this._checkChangeHandler = null;
}
/**
* Get the checkbox object for the specified checkbox.
*
* The checkbox is looked up by the specified id or selector, which must
* match what was specified during construction.
*
* @param {String} idOrSelector Identifying string
* @return {Object} Object for checkbox, or null if not found
*/
checkBox(idOrSelector) {
return this._group.find(
(el) => el.id === idOrSelector || el.selector === idOrSelector
);
}
/**
* Set a handler for checkbox changes.
*
* Handler will be called with same result as would be found by checkBox().
*
* @param {Function} handler function (checbox)
*/
set onCheckChange(handler) {
this._checkChangeHandler = handler;
}
}

View File

@@ -73,18 +73,19 @@ export default class RecordingsView {
* Construct display from camera data and use supplied formatter.
*
* @param {Camera} camera camera object (immutable)
* @param {String} streamType "main" or "sub"
* @param {RecordingFormatter} recordingFormatter Desired formatter
* @param {Boolean} trimmed True if the display should include trimmed ranges
* @param {jQuery} parent Parent to which new DOM is attached, or null
*/
constructor(camera, recordingFormatter, trimmed = false, parent = null) {
constructor(camera, streamType, recordingFormatter, trimmed = false,
parent = null) {
this._cameraName = camera.shortName;
this._cameraRange = camera.range90k;
this._formatter = recordingFormatter;
this._element = $(`tab-${camera.uuid}`); // Might not be there initially
if (this._element.length == 0) {
this._element = this._createElement(camera.uuid, camera.shortName);
}
const id = `tab-${camera.uuid}-${streamType}`;
this._element = this._createElement(id, camera.shortName, streamType);
this._trimmed = trimmed;
this._recordings = null;
this._recordingsRange = null;
@@ -100,12 +101,14 @@ export default class RecordingsView {
*
* @param {String} id DOM id for the main element
* @param {String} cameraName Name of the corresponding camera
* @param {String} streamType "main" or "sub"
* @return {jQuery} Partial DOM as jQuery object
*/
_createElement(id, cameraName) {
_createElement(id, cameraName, streamType) {
const tab = $('<tbody>').attr('id', id);
tab.append(
$('<tr class="name">').append($('<th colspan=6/>').text(cameraName)),
$('<tr class="name">').append($('<th colspan=6/>')
.text(cameraName + ' ' + streamType)),
$('<tr class="hdr">').append(
$(
_columnOrder

View File

@@ -0,0 +1,99 @@
// vim: set et sw=2 ts=2:
//
// This file is part of Moonfire NVR, a security camera digital video recorder.
// Copyright (C) 2018 Dolf Starreveld <dolf@starreveld.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import $ from 'jquery';
const allStreamTypes = ['main', 'sub'];
/**
* View for selecting the enabled streams.
*
* This displays a table with a camera per row and stream type per column.
* It propagates the enabled status on to the stream view. It also calls
* the optional onChange handler on any change.
*/
export default class StreamSelectorView {
/**
* @param {Array} cameras An element for each camera with
* - camera: a {Camera}
* - streamViews: a map of stream type to {StreamView}
* @param {jQuery} parent jQuery parent element to append to
*/
constructor(cameras, parent) {
this._cameras = cameras;
if (cameras.length !== 0) {
// Add a header row.
let hdr = $('<tr/>').append($('<th/>'));
for (const streamType of allStreamTypes) {
hdr.append($('<th/>').text(streamType));
}
parent.append(hdr);
}
this._cameras.forEach((c) => {
let row = $('<tr/>').append($('<td>').text(c.camera.shortName));
let firstStreamType = true;
for (const streamType of allStreamTypes) {
const streamView = c.streamViews[streamType];
if (streamView === undefined) {
row.append('<td/>');
} else {
const id = 'cam-' + c.camera.uuid + '-' + streamType;
let cb = $('<input type="checkbox">').attr('name', id).attr('id', id);
// Only the first stream type for each camera should be checked
// initially.
cb.prop('checked', firstStreamType);
streamView.enabled = firstStreamType;
firstStreamType = false;
cb.change((e) => {
streamView.enabled = e.target.checked;
if (this._onChangeHandler) {
this._onChangeHandler();
}
});
row.append($('<td/>').append(cb));
}
}
parent.append(row);
});
this._onChangeHandler = null;
}
/** @param {function()} handler a handler to run after toggling a stream */
set onChange(handler) {
this._onChangeHandler = handler;
}
}

View File

@@ -33,29 +33,29 @@
import RecordingsView from './RecordingsView';
/**
* Class handling a camer view.
*
* A camera view consists of a list of available recording segments for
* playback.
* Stream view: a list of available recording segments for playback.
*/
export default class CameraView {
export default class StreamView {
/**
* Construct the view.
*
* @param {Camera} cameraModel Model object for camera
* @param {String} streamType "main" or "sub"
* @param {[type]} recordingFormatter Formatter to be used by recordings
* @param {[type]} trimmed True if rec. ranges should be trimmed
* @param {[type]} recordingsParent Parent element to attach to or null)
*/
constructor(
cameraModel,
streamType,
recordingFormatter,
trimmed,
recordingsParent = null
) {
this.camera = cameraModel;
this.streamType = streamType;
this.stream = cameraModel.streams[streamType];
this.recordingsView = new RecordingsView(
this.camera,
this.streamType,
recordingFormatter,
trimmed,
recordingsParent
@@ -82,11 +82,8 @@ export default class CameraView {
set enabled(enabled) {
this._enabled = enabled;
this.recordingsView.show = enabled;
console.log(
'Camera %s %s',
this.camera.shortName,
this.enabled ? 'enabled' : 'disabled'
);
console.log('Stream %s-%s %s', this.camera.shortName, this.streamType,
this.enabled ? 'enabled' : 'disabled');
}
/**