diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..0eca927 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "extends": "google", + "rules": { + } +} diff --git a/.gitignore b/.gitignore index 5cbb075..0123e42 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ cameras.sql .project .settings *.swp +*.sublime-workspace node_modules prep.config settings-nvr-local.js diff --git a/.jsbeautifyrc b/.jsbeautifyrc new file mode 100644 index 0000000..74d87d6 --- /dev/null +++ b/.jsbeautifyrc @@ -0,0 +1,43 @@ +{ + "indent_size": 2, + "indent_char": " ", + "indent_with_tabs": false, + "eol": "\n", + "end_with_newline": true, + "indent_level": 0, + "preserve_newlines": true, + "max_preserve_newlines": 4, + "space_in_paren": false, + "space_in_empty_paren": false, + "jslint_happy": false, + "space_after_anon_function": false, + "brace_style": "collapse,preserve-inline", + "unindent_chained_methods": false, + "break_chained_methods": false, + "keep_array_indentation": false, + "unescape_strings": false, + "wrap_line_length": 0, + "e4x": false, + "comma_first": false, + "operator_position": "before-newline", + "js": { + "indent_size": 2, + "indent_char": " ", + "indent_with_tabs": false + }, + "json": { + "brace_style": "expand", + "keep_array_indentation": false, + "unescape_strings": false + }, + "custom": { + "package?(-lock).json": { + "indent_size": 2, + "brace_style": "collapse" + }, + "*.sublime-@(settings|keymap|commands|menu)": { + "indent_size": 4, + "brace_style": "expand" + } + } +} diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..642e70a --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,37 @@ +{ + "printWidth": 80, + "singleQuote": false, + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "parser": "babylon", + "semi": true, + "requirePragma": false, + "proseWrap": "preserve", + "arrowParens": "avoid", + "tabWidth": 4, + "overrides": [ + { + "files": ["*.js"], + "options": { + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "semi": true, + "arrowParens": "always" + } + }, + { + "files": ["*.json", "moonfire.sublime-project"], + "options": { + "parser": "json", + "tabWidth": 4, + "useTabs": false, + "singleQuote": false, + "trailingComma": "none" + } + } + ] +} diff --git a/moonfire.sublime-project b/moonfire.sublime-project new file mode 100644 index 0000000..17839ee --- /dev/null +++ b/moonfire.sublime-project @@ -0,0 +1,42 @@ +{ + "folders": [ + { + "path": ".", + "folder_exclude_patterns": [ + "design", + "ffmpeg", + "node_modules", + "ui-dist" + ], + "file_exclude_patterns": ["*.png", "*.jpg", "*.gif"] + } + ], + "settings": { + "js_prettier": { + "additional_cli_args": { + "--config-precedence": "prefer-file" + }, + "debug": true, + "prettier_cli_path": "", + "node_path": "", + "auto_format_on_save": false, + "auto_format_on_save_excludes": [], + "auto_format_on_save_requires_prettier_config": false, + "allow_inline_formatting": false, + "custom_file_extensions": [], + "max_file_size_limit": -1, + "prettier_options": { + "printWidth": 80, + "singleQuote": false, + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "parser": "babylon", + "semi": true, + "requirePragma": false, + "proseWrap": "preserve", + "arrowParens": "avoid" + } + } + } +} diff --git a/package.json b/package.json index 1552e2b..5882b6a 100644 --- a/package.json +++ b/package.json @@ -23,10 +23,16 @@ "babel-minify-webpack-plugin": "^0.3.0", "babel-preset-env": "^1.6.1", "clean-webpack-plugin": "^0.1.18", + "compression-webpack-plugin": "^1.1.10", "css-loader": "^0.28.10", + "eslint": "^4.18.2", + "eslint-config-google": "^0.9.1", "file-loader": "^1.1.11", + "html-loader": "^0.5.5", "html-webpack-plugin": "^3.0.6", + "prettier": "1.11.1", "style-loader": "^0.19.0", + "uglifyjs-webpack-plugin": "^1.2.3", "webpack": "^4.0.1", "webpack-cli": "^2.0.10", "webpack-dev-server": "^3.1.0", diff --git a/settings-nvr.js b/settings-nvr.js index 84f8323..266e64b 100644 --- a/settings-nvr.js +++ b/settings-nvr.js @@ -1,5 +1,34 @@ -// vim: set et ts=2 sw=2: +// 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 +// +// 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 . /** * This module must export a map, but can use a function with no arguments diff --git a/ui-src/NVRApplication.js b/ui-src/NVRApplication.js new file mode 100644 index 0000000..38e90f4 --- /dev/null +++ b/ui-src/NVRApplication.js @@ -0,0 +1,306 @@ +// 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 +// +// 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 . + +// TODO: test abort. +// TODO: add error bar on fetch failure. +// 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'; + +// This causes our custom css to be loaded after the above! +require('./assets/index.css'); + +import $ from 'jquery'; +import 'jquery-ui/ui/widgets/datepicker'; +import 'jquery-ui/ui/widgets/dialog'; +import 'jquery-ui/ui/widgets/tooltip'; + +import Camera from './lib/models/Camera'; +import CameraView from './lib/views/CameraView'; +import CalendarView from './lib/views/CalendarView'; +import NVRSettingsView from './lib/views/NVRSettingsView'; +import CheckboxGroupView from './lib/views/CheckboxGroupView'; +import RecordingFormatter from './lib/support/RecordingFormatter'; +import TimeFormatter, { + TimeStamp90kFormatter, +} from './lib/support/TimeFormatter'; +import MoonfireAPI from './lib/MoonfireAPI'; + +const api = new MoonfireAPI(); +let cameraViews = null; // CameraView objects +let calendarView = null; // CalendarView object + +/** + * Currently selected time format specification. + * + * @type {String} + */ +let timeFmt = 'YYYY-MM-DD HH:mm:ss'; + +/** + * Currently active time formatter. + * This is lazy initialized at the point we receive the timezone information + * and never changes afterwards, except possibly for changing the timezone. + * + * @type {[type]} + */ +let timeFormatter = null; + +/** + * Currently active time formatter for internal time format. + * This is lazy initialized at the point we receive the timezone information + * and never changes afterwards, except possibly for changing the timezone. + * + * @type {[type]} + */ +let timeFormatter90k = null; + +/** + * Globally set a new timezone for the app. + * + * @param {String} timeZone Timezone name + */ +function newTimeZone(timeZone) { + timeFormatter = new TimeFormatter(timeFmt, timeZone); + timeFormatter90k = new TimeStamp90kFormatter(timeZone); +} + +/** + * Globally set a new time format for the app. + * + * @param {String} format Time format specification + */ +function newTimeFormat(format) { + timeFormatter = new TimeFormatter(format, timeFormatter.tz); +} + +/** + * Event handler for clicking on a video. + * + * A 'dialog' object is attached to the body of the dom and it + * properly initialized with the corrcet src url. + * + * @param {NVRSettings} nvrSettingsView NVRSettingsView in effect + * @param {object} camera Object for the camera + * @param {object} range Range Object + * @param {object} recording Recording object + * @return {void} + */ +function onSelectVideo(nvrSettingsView, camera, range, recording) { + console.log('Recording clicked: ', recording); + const trimmedRange = recording.range90k(nvrSettingsView.trim ? range : null); + const url = api.videoPlayUrl( + camera.uuid, + recording, + trimmedRange, + nvrSettingsView.timeStampTrack + ); + const video = $('