// vim: set et sw=2:
// TODO: test abort.
// TODO: add error bar on fetch failure.
// TODO: style: no globals? string literals? line length? fn comments?
// TODO: live updating.
import 'jquery-ui/themes/base/button.css';
import 'jquery-ui/themes/base/core.css';
import 'jquery-ui/themes/base/datepicker.css';
import 'jquery-ui/themes/base/dialog.css';
import 'jquery-ui/themes/base/resizable.css';
import 'jquery-ui/themes/base/theme.css';
import 'jquery-ui/themes/base/tooltip.css';
import $ from 'jquery';
import 'jquery-ui/ui/widgets/datepicker';
import 'jquery-ui/ui/widgets/dialog';
import 'jquery-ui/ui/widgets/tooltip';
import moment from 'moment-timezone';
const apiUrl = '/api/';
// IANA timezone name.
let zone = null;
// A dict describing the currently selected range.
let selectedRange = {
startDateStr: null, // null or YYYY-MM-DD
startTimeStr: '', // empty or HH:mm[:ss[:FFFFF]][+-HHmm]
startTime90k: null, // null or 90k units since epoch
endDateStr: null, // null or YYYY-MM-DD
endTimeStr: '', // empty or HH:mm[:ss[:FFFFF]][+-HHmm]
endTime90k: null, // null or 90k units since epoch
singleDateStr: null, // if startDateStr===endDateStr, that value, otherwise null
};
// Cameras is a dictionary as retrieved from apiUrl + some extra props:
// * "enabled" is a boolean indicating if the camera should be displayed and
// if it should be used to constrain the datepickers.
// * "recordingsUrl" is null or the currently fetched/fetching .../recordings url.
// * "recordingsRange" is a null or a dict (in the same format as
// selectedRange) describing what is fetching/fetched.
// * "recordingsData" is null or the data fetched from "recordingsUrl".
// * "recordingsReq" is null or a jQuery ajax object of an active .../recordings
// request if currently fetching.
let cameras = null;
function req(url) {
return $.ajax(url, {
dataType: 'json',
headers: {'Accept': 'application/json'},
});
}
// Produces a human-readable but unambiguous format of the given timestamp:
// ISO-8601 plus an extra colon component after the seconds indicating the
// fractional time in 90,000ths of a second.
function formatTime(ts90k) {
const m = moment.tz(ts90k / 90, zone);
const frac = ts90k % 90000;
return m.format('YYYY-MM-DDTHH:mm:ss:' + String(100000 + frac).substr(1) + 'Z');
}
function onSelectVideo(camera, range, recording) {
let url = apiUrl + 'cameras/' + camera.uuid + '/view.mp4?s=' + recording.startId;
if (recording.endId !== undefined) {
url += '-' + recording.endId;
}
const trim = $("#trim").prop("checked");
let rel = '';
let startTime90k = recording.startTime90k;
if (trim && recording.startTime90k < range.startTime90k) {
rel += range.startTime90k - recording.startTime90k;
startTime90k = range.startTime90k;
}
rel += '-';
let endTime90k = recording.endTime90k;
if (trim && recording.endTime90k > range.endTime90k) {
rel += range.endTime90k - recording.startTime90k;
endTime90k = range.endTime90k;
}
if (rel !== '-') {
url += '.' + rel;
}
if ($("#ts").prop("checked")) {
url += '&ts=true';
}
console.log('Displaying video: ', url);
let video = $('');
let dialog = $('
').append(video);
$("body").append(dialog);
// Format start and end times for the dialog title. If they're the same day,
// abbreviate the end time.
let formattedStart = formatTime(startTime90k);
let formattedEnd = formatTime(endTime90k);
let timePos = 'YYYY-mm-ddT'.length;
if (formattedEnd.startsWith(formattedStart.substr(0, timePos))) {
formattedEnd = formattedEnd.substr(timePos);
}
dialog.dialog({
title: camera.shortName + ", " + formattedStart + " to " + formattedEnd,
width: recording.videoSampleEntryWidth / 4,
close: function() { dialog.remove(); },
});
video.attr("src", url);
}
function formatRecordings(camera) {
let tbody = $("#tab-" + camera.uuid);
$(".loading", tbody).hide();
$(".r", tbody).remove();
const frameRateFmt = new Intl.NumberFormat([], {maximumFractionDigits: 0});
const sizeFmt = new Intl.NumberFormat([], {maximumFractionDigits: 1});
const trim = $("#trim").prop("checked");
for (let recording of camera.recordingsData.recordings) {
const duration = (recording.endTime90k - recording.startTime90k) / 90000;
let row = $('