mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-11-20 01:50:24 -05:00
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:
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
99
ui-src/lib/views/StreamSelectorView.js
Normal file
99
ui-src/lib/views/StreamSelectorView.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
Reference in New Issue
Block a user