A little more UI refactor, cleanup, eslint more strict (#54)
* A little more UI refactor, cleanup, eslint more strict * Split out imports for jQuery components and put them where needed. * No longer do all of it in application module. * Prepares better for code splitting. * Split out video player dialog * Simplifies jquery-ui dependencies for code splitting * Simplifies code * Configure to generate more, but smaller bundles. * Setup some more strict eslint settings * Fix css to import rather than require * Change settings to correctly support tree shaking in production build Signed-off-by: Dolf Starreveld <dolf@starreveld.com> * Remove “old” code from TimeFormatter * Accidentally left behind due to overlapping PRs Signed-off-by: Dolf Starreveld <dolf@starreveld.com>
This commit is contained in:
parent
eaae640703
commit
f5aa0080bb
|
@ -3,7 +3,20 @@
|
||||||
"ecmaVersion": 6,
|
"ecmaVersion": 6,
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
|
"env": {
|
||||||
|
"es6": true,
|
||||||
|
"browser": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
"extends": "google",
|
"extends": "google",
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"init-declarations": ["error", "always"],
|
||||||
|
"no-catch-shadow": ["error"],
|
||||||
|
"no-delete-var": ["error"],
|
||||||
|
"no-shadow": ["error", { "builtinGlobals": false, "hoist": "functions", "allow": [] }],
|
||||||
|
"no-shadow-restricted-names": ["error"],
|
||||||
|
"no-undef": ["error", {"typeof": true}],
|
||||||
|
"no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }],
|
||||||
|
"no-use-before-define": ["error", { "functions": true, "classes": true }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-loader": "^7.1.4",
|
"babel-loader": "^7.1.4",
|
||||||
"babel-minify-webpack-plugin": "^0.3.0",
|
"babel-minify-webpack-plugin": "^0.3.0",
|
||||||
|
"babel-plugin-transform-imports": "^1.5.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"clean-webpack-plugin": "^0.1.18",
|
"clean-webpack-plugin": "^0.1.18",
|
||||||
"compression-webpack-plugin": "^1.1.10",
|
"compression-webpack-plugin": "^1.1.10",
|
||||||
|
|
|
@ -34,31 +34,32 @@
|
||||||
// TODO: add error bar on fetch failure.
|
// TODO: add error bar on fetch failure.
|
||||||
// TODO: live updating.
|
// TODO: live updating.
|
||||||
|
|
||||||
import 'jquery-ui/themes/base/button.css';
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
// tooltip needs:
|
||||||
|
// css.structure: ../../themes/base/core.css
|
||||||
|
// css.structure: ../../themes/base/tooltip.css
|
||||||
|
// css.theme: ../../themes/base/theme.css
|
||||||
|
|
||||||
import 'jquery-ui/themes/base/core.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 'jquery-ui/themes/base/tooltip.css';
|
||||||
|
import 'jquery-ui/themes/base/theme.css';
|
||||||
|
|
||||||
// This causes our custom css to be loaded after the above!
|
// This causes our custom css to be loaded after the above!
|
||||||
require('./assets/index.css');
|
import './assets/index.css';
|
||||||
|
|
||||||
import $ from 'jquery';
|
// Get ui widgets themselves
|
||||||
import 'jquery-ui/ui/widgets/datepicker';
|
|
||||||
import 'jquery-ui/ui/widgets/dialog';
|
|
||||||
import 'jquery-ui/ui/widgets/tooltip';
|
import 'jquery-ui/ui/widgets/tooltip';
|
||||||
|
|
||||||
import Camera from './lib/models/Camera';
|
import Camera from './lib/models/Camera';
|
||||||
import CameraView from './lib/views/CameraView';
|
import CameraView from './lib/views/CameraView';
|
||||||
import CalendarView from './lib/views/CalendarView';
|
import CalendarView from './lib/views/CalendarView';
|
||||||
|
import VideoDialogView from './lib/views/VideoDialogView';
|
||||||
import NVRSettingsView from './lib/views/NVRSettingsView';
|
import NVRSettingsView from './lib/views/NVRSettingsView';
|
||||||
import CheckboxGroupView from './lib/views/CheckboxGroupView';
|
import CheckboxGroupView from './lib/views/CheckboxGroupView';
|
||||||
import RecordingFormatter from './lib/support/RecordingFormatter';
|
import RecordingFormatter from './lib/support/RecordingFormatter';
|
||||||
import TimeFormatter, {
|
import TimeFormatter from './lib/support/TimeFormatter';
|
||||||
TimeStamp90kFormatter,
|
import TimeStamp90kFormatter from './lib/support/TimeStamp90kFormatter';
|
||||||
} from './lib/support/TimeFormatter';
|
|
||||||
import MoonfireAPI from './lib/MoonfireAPI';
|
import MoonfireAPI from './lib/MoonfireAPI';
|
||||||
|
|
||||||
const api = new MoonfireAPI();
|
const api = new MoonfireAPI();
|
||||||
|
@ -130,27 +131,18 @@ function onSelectVideo(nvrSettingsView, camera, range, recording) {
|
||||||
trimmedRange,
|
trimmedRange,
|
||||||
nvrSettingsView.timeStampTrack
|
nvrSettingsView.timeStampTrack
|
||||||
);
|
);
|
||||||
const video = $('<video controls preload="auto" autoplay="true" />');
|
const [
|
||||||
const dialog = $('<div class="playdialog" />').append(video);
|
formattedStart,
|
||||||
$('body').append(dialog);
|
formattedEnd,
|
||||||
|
] = timeFormatter90k.formatSameDayShortened(
|
||||||
let [formattedStart, formattedEnd] = timeFormatter90k.formatSameDayShortened(
|
|
||||||
trimmedRange.startTime90k,
|
trimmedRange.startTime90k,
|
||||||
trimmedRange.endTime90k
|
trimmedRange.endTime90k
|
||||||
);
|
);
|
||||||
dialog.dialog({
|
const videoTitle =
|
||||||
title: camera.shortName + ', ' + formattedStart + ' to ' + formattedEnd,
|
camera.shortName + ', ' + formattedStart + ' to ' + formattedEnd;
|
||||||
width: recording.videoSampleEntryWidth / 4,
|
new VideoDialogView()
|
||||||
close: () => {
|
.attach($('body'))
|
||||||
const videoDOMElement = video[0];
|
.play(videoTitle, recording.videoSampleEntryWidth / 4, url);
|
||||||
videoDOMElement.pause();
|
|
||||||
videoDOMElement.src = ''; // Remove current source to stop loading
|
|
||||||
dialog.remove();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Now that dialog is up, set the src so video starts
|
|
||||||
console.log('Video url: ' + url);
|
|
||||||
video.attr('src', url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -193,16 +185,19 @@ function fetch(selectedRange, videoLength) {
|
||||||
cameraView.recordingsReq = null;
|
cameraView.recordingsReq = null;
|
||||||
});
|
});
|
||||||
r
|
r
|
||||||
.then(function(data, status, req) {
|
.then(function(data /* , status, req */) {
|
||||||
// Sort recordings in descending order.
|
// Sort recordings in descending order.
|
||||||
data.recordings.sort(function(a, b) {
|
data.recordings.sort(function(a, b) {
|
||||||
return b.startId - a.startId;
|
return b.startId - a.startId;
|
||||||
});
|
});
|
||||||
console.log('Fetched results > updating recordings');
|
console.log(
|
||||||
|
'Fetched results for "%s" > updating recordings',
|
||||||
|
cameraView.camera.shortName
|
||||||
|
);
|
||||||
cameraView.recordingsJSON = data.recordings;
|
cameraView.recordingsJSON = data.recordings;
|
||||||
})
|
})
|
||||||
.catch(function(data, status, err) {
|
.catch(function(data, status, err) {
|
||||||
console.log(url, ' load failed: ', status, ': ', err);
|
console.error(url, ' load failed: ', status, ': ', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,11 +290,11 @@ export default class NVRApplication {
|
||||||
.request(api.nvrUrl(true))
|
.request(api.nvrUrl(true))
|
||||||
.done((data) => onReceivedCameras(data))
|
.done((data) => onReceivedCameras(data))
|
||||||
.fail((req, status, err) => {
|
.fail((req, status, err) => {
|
||||||
console.log('NVR load error: ', status, err);
|
console.error('NVR load error: ', status, err);
|
||||||
onReceivedCameras({cameras: []});
|
onReceivedCameras({cameras: []});
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.log('NVR load exception: ', e);
|
console.error('NVR load exception: ', e);
|
||||||
onReceivedCameras({cameras: []});
|
onReceivedCameras({cameras: []});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ body {
|
||||||
#nav {
|
#nav {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
.ui-datepicker {
|
|
||||||
|
#datetime .ui-datepicker {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
import NVRApplication from './NVRApplication';
|
import NVRApplication from './NVRApplication';
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import './favicon.ico';
|
||||||
|
|
||||||
// On document load, start application
|
// On document load, start application
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default class MoonfireAPI {
|
||||||
url.hostname = host;
|
url.hostname = host;
|
||||||
url.port = port;
|
url.port = port;
|
||||||
console.log('API: ' + url.origin + url.pathname);
|
console.log('API: ' + url.origin + url.pathname);
|
||||||
this._builder = new URLBuilder(url.origin + url.pathname);
|
this._builder = new URLBuilder(url.origin + url.pathname, relativeUrls);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import Time90kParser from '../support/Time90kParser';
|
import Time90kParser from '../support/Time90kParser';
|
||||||
import {TimeStamp90kFormatter} from '../support/TimeFormatter';
|
import TimeStamp90kFormatter from '../support/TimeStamp90kFormatter';
|
||||||
import Range90k from './Range90k';
|
import Range90k from './Range90k';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default class Range {
|
||||||
*/
|
*/
|
||||||
constructor(low, high) {
|
constructor(low, high) {
|
||||||
if (high < low) {
|
if (high < low) {
|
||||||
console.log('Warning range swap: ' + low + ' - ' + high);
|
console.warn('Warning range swap: ' + low + ' - ' + high);
|
||||||
[low, high] = [high, low];
|
[low, high] = [high, low];
|
||||||
}
|
}
|
||||||
this.low = low;
|
this.low = low;
|
||||||
|
@ -65,7 +65,7 @@ export default class Range {
|
||||||
* Determine if value is inside the range.
|
* Determine if value is inside the range.
|
||||||
*
|
*
|
||||||
* @param {Number} value Value to test
|
* @param {Number} value Value to test
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
isInRange(value) {
|
isInRange(value) {
|
||||||
return value >= this.low && value <= this.high;
|
return value >= this.low && value <= this.high;
|
||||||
|
|
|
@ -40,9 +40,10 @@ import Range from './Range';
|
||||||
let _range = new WeakMap();
|
let _range = new WeakMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclass of Range to represent ranges over timestamps in 90k format.
|
* Class like Range to represent ranges over timestamps in 90k format.
|
||||||
*
|
*
|
||||||
* This mostly means added some getters with names that make more sense.
|
* A composed member of the Range class is use for the heavy lifting, while
|
||||||
|
* this class provides a different interface.
|
||||||
*/
|
*/
|
||||||
export default class Range90k {
|
export default class Range90k {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
export const internalTimeFormat = 'YYYY-MM-DDTHH:mm:ss:FFFFFZ';
|
|
||||||
export const defaultTimeFormat = 'YYYY-MM-DD HH:mm:ss';
|
export const defaultTimeFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,50 +117,3 @@ export default class TimeFormatter {
|
||||||
return moment.tz(ms, this._tz).format(format);
|
return moment.tz(ms, this._tz).format(format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Specialized class similar to TimeFormatter but forcing a specific time format
|
|
||||||
* for internal usage purposes.
|
|
||||||
*/
|
|
||||||
export class TimeStamp90kFormatter {
|
|
||||||
/**
|
|
||||||
* Construct from just a timezone specification.
|
|
||||||
*
|
|
||||||
* @param {String} tz Timezone
|
|
||||||
*/
|
|
||||||
constructor(tz) {
|
|
||||||
this._formatter = new TimeFormatter(internalTimeFormat, tz);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a timestamp in 90k units using internal format.
|
|
||||||
*
|
|
||||||
* @param {Number} ts90k timestamp in 90,000ths of a second resolution
|
|
||||||
* @return {String} Formatted timestamp
|
|
||||||
*/
|
|
||||||
formatTimeStamp90k(ts90k) {
|
|
||||||
return this._formatter.formatTimeStamp90k(ts90k);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given two timestamp return formatted versions of both, where the second
|
|
||||||
* one may have been shortened if it falls on the same date as the first one.
|
|
||||||
*
|
|
||||||
* @param {Number} ts1 First timestamp in 90k units
|
|
||||||
* @param {Number} ts2 Secodn timestamp in 90k units
|
|
||||||
* @return {Array} Array with two elements: [ ts1Formatted, ts2Formatted ]
|
|
||||||
*/
|
|
||||||
formatSameDayShortened(ts1, ts2) {
|
|
||||||
let ts1Formatted = this.formatTimeStamp90k(ts1);
|
|
||||||
let ts2Formatted = this.formatTimeStamp90k(ts2);
|
|
||||||
let timePos = this._formatter.formatStr.indexOf('T');
|
|
||||||
if (timePos != -1) {
|
|
||||||
const datePortion = ts1Formatted.substr(0, timePos);
|
|
||||||
ts1Formatted = datePortion + ' ' + ts1Formatted.substr(timePos + 1);
|
|
||||||
if (ts2Formatted.startsWith(datePortion)) {
|
|
||||||
ts2Formatted = ts2Formatted.substr(timePos + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [ts1Formatted, ts2Formatted];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
// 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 TimeFormatter from './TimeFormatter';
|
||||||
|
|
||||||
|
|
||||||
|
export const internalTimeFormat = 'YYYY-MM-DDTHH:mm:ss:FFFFFZ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized class similar to TimeFormatter but forcing a specific time format
|
||||||
|
* for internal usage purposes.
|
||||||
|
*/
|
||||||
|
export default class TimeStamp90kFormatter {
|
||||||
|
/**
|
||||||
|
* Construct from just a timezone specification.
|
||||||
|
*
|
||||||
|
* @param {String} tz Timezone
|
||||||
|
*/
|
||||||
|
constructor(tz) {
|
||||||
|
this._formatter = new TimeFormatter(internalTimeFormat, tz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a timestamp in 90k units using internal format.
|
||||||
|
*
|
||||||
|
* @param {Number} ts90k timestamp in 90,000ths of a second resolution
|
||||||
|
* @return {String} Formatted timestamp
|
||||||
|
*/
|
||||||
|
formatTimeStamp90k(ts90k) {
|
||||||
|
return this._formatter.formatTimeStamp90k(ts90k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two timestamp return formatted versions of both, where the second
|
||||||
|
* one may have been shortened if it falls on the same date as the first one.
|
||||||
|
*
|
||||||
|
* @param {Number} ts1 First timestamp in 90k units
|
||||||
|
* @param {Number} ts2 Secodn timestamp in 90k units
|
||||||
|
* @return {Array} Array with two elements: [ ts1Formatted, ts2Formatted ]
|
||||||
|
*/
|
||||||
|
formatSameDayShortened(ts1, ts2) {
|
||||||
|
let ts1Formatted = this.formatTimeStamp90k(ts1);
|
||||||
|
let ts2Formatted = this.formatTimeStamp90k(ts2);
|
||||||
|
let timePos = this._formatter.formatStr.indexOf('T');
|
||||||
|
if (timePos != -1) {
|
||||||
|
const datePortion = ts1Formatted.substr(0, timePos);
|
||||||
|
ts1Formatted = datePortion + ' ' + ts1Formatted.substr(timePos + 1);
|
||||||
|
if (ts2Formatted.startsWith(datePortion)) {
|
||||||
|
ts2Formatted = ts2Formatted.substr(timePos + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [ts1Formatted, ts2Formatted];
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,20 +31,10 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
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 'jquery-ui/ui/widgets/datepicker';
|
|
||||||
import 'jquery-ui/ui/widgets/dialog';
|
|
||||||
import 'jquery-ui/ui/widgets/tooltip';
|
|
||||||
|
|
||||||
import DatePickerView from './DatePickerView';
|
import DatePickerView from './DatePickerView';
|
||||||
import CalendarTSRange from '../models/CalendarTSRange';
|
import CalendarTSRange from '../models/CalendarTSRange';
|
||||||
import {TimeStamp90kFormatter} from '../support/TimeFormatter';
|
import TimeStamp90kFormatter from '../support/TimeStamp90kFormatter';
|
||||||
import Time90kParser from '../support/Time90kParser';
|
import Time90kParser from '../support/Time90kParser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -177,10 +167,11 @@ export default class CalendarView {
|
||||||
|
|
||||||
if (this._sameDay) {
|
if (this._sameDay) {
|
||||||
fromPickerView.option({
|
fromPickerView.option({
|
||||||
dateFormat: $.datepicker.ISO_8601,
|
dateFormat: DatePickerView.datepicker.ISO_8601,
|
||||||
minDate: minDateStr,
|
minDate: minDateStr,
|
||||||
maxDate: maxDateStr,
|
maxDate: maxDateStr,
|
||||||
onSelect: (dateStr, picker) => this._updateRangeDates(dateStr, dateStr),
|
onSelect: (dateStr /* , picker */) =>
|
||||||
|
this._updateRangeDates(dateStr, dateStr),
|
||||||
beforeShowDay: beforeShowDay,
|
beforeShowDay: beforeShowDay,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
});
|
});
|
||||||
|
@ -188,21 +179,21 @@ export default class CalendarView {
|
||||||
toPickerView.activate(); // Default state, but alive
|
toPickerView.activate(); // Default state, but alive
|
||||||
} else {
|
} else {
|
||||||
fromPickerView.option({
|
fromPickerView.option({
|
||||||
dateFormat: $.datepicker.ISO_8601,
|
dateFormat: DatePickerView.datepicker.ISO_8601,
|
||||||
minDate: minDateStr,
|
minDate: minDateStr,
|
||||||
onSelect: (dateStr, picker) => {
|
onSelect: (dateStr /* , picker */) => {
|
||||||
toPickerView.option('minDate', this.fromDateISO);
|
toPickerView.minDate = this.fromDateISO;
|
||||||
this._updateRangeDates(dateStr, this.toDateISO);
|
this._updateRangeDates(dateStr, this.toDateISO);
|
||||||
},
|
},
|
||||||
beforeShowDay: beforeShowDay,
|
beforeShowDay: beforeShowDay,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
});
|
});
|
||||||
toPickerView.option({
|
toPickerView.option({
|
||||||
dateFormat: $.datepicker.ISO_8601,
|
dateFormat: DatePickerView.datepicker.ISO_8601,
|
||||||
minDate: fromPickerView.dateISO,
|
minDate: fromPickerView.dateISO,
|
||||||
maxDate: maxDateStr,
|
maxDate: maxDateStr,
|
||||||
onSelect: (dateStr, picker) => {
|
onSelect: (dateStr /* , picker */) => {
|
||||||
fromPickerView.option('maxDate', this.toDateISO);
|
fromPickerView.maxDate = this.toDateISO;
|
||||||
this._updateRangeDates(this.fromDateISO, dateStr);
|
this._updateRangeDates(this.fromDateISO, dateStr);
|
||||||
},
|
},
|
||||||
beforeShowDay: beforeShowDay,
|
beforeShowDay: beforeShowDay,
|
||||||
|
@ -228,7 +219,7 @@ export default class CalendarView {
|
||||||
* The change requires updating the selected range and then informing
|
* The change requires updating the selected range and then informing
|
||||||
* any listeners that the range has changed (so they can update data).
|
* any listeners that the range has changed (so they can update data).
|
||||||
*
|
*
|
||||||
* @param {Object} event Time Event on DOM that triggered calling this
|
* @param {event} event DOM event that triggered us
|
||||||
* @param {Boolean} isEnd True if this is for end time
|
* @param {Boolean} isEnd True if this is for end time
|
||||||
*/
|
*/
|
||||||
_pickerTimeChanged(event, isEnd) {
|
_pickerTimeChanged(event, isEnd) {
|
||||||
|
@ -239,7 +230,7 @@ export default class CalendarView {
|
||||||
? selectedRange.setEndTime(newTimeStr)
|
? selectedRange.setEndTime(newTimeStr)
|
||||||
: selectedRange.setStartTime(newTimeStr);
|
: selectedRange.setStartTime(newTimeStr);
|
||||||
if (parsedTS === null) {
|
if (parsedTS === null) {
|
||||||
console.log('bad time change');
|
console.warn('bad time change');
|
||||||
$(pickerElement).addClass('ui-state-error');
|
$(pickerElement).addClass('ui-state-error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ export default class CameraView {
|
||||||
this._enabled = enabled;
|
this._enabled = enabled;
|
||||||
this.recordingsView.show = enabled;
|
this.recordingsView.show = enabled;
|
||||||
console.log(
|
console.log(
|
||||||
'Camera ',
|
'Camera %s %s',
|
||||||
this.camera.shortName,
|
this.camera.shortName,
|
||||||
this.enabled ? 'enabled' : 'disabled'
|
this.enabled ? 'enabled' : 'disabled'
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,10 +32,27 @@
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
|
import 'jquery-ui/themes/base/core.css';
|
||||||
|
import 'jquery-ui/themes/base/datepicker.css';
|
||||||
|
import 'jquery-ui/themes/base/theme.css';
|
||||||
|
import 'jquery-ui/ui/widgets/datepicker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to encapsulate datepicker UI widget from jQuery.
|
* Class to encapsulate datepicker UI widget from jQuery.
|
||||||
*/
|
*/
|
||||||
export default class DatePickerView {
|
export default class DatePickerView {
|
||||||
|
/**
|
||||||
|
* Get the singleton datepicker instance.
|
||||||
|
*
|
||||||
|
* This is useful for accessing implementation constants, such as
|
||||||
|
* date formats etc.
|
||||||
|
*
|
||||||
|
* @return {jQuery.datepicker} JQuery datepicker instance
|
||||||
|
*/
|
||||||
|
static get datepicker() {
|
||||||
|
return $.datepicker;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct wapper an attach to a specified parent DOM node.
|
* Construct wapper an attach to a specified parent DOM node.
|
||||||
*
|
*
|
||||||
|
@ -85,11 +102,11 @@ export default class DatePickerView {
|
||||||
*
|
*
|
||||||
* @return {Any} Function result
|
* @return {Any} Function result
|
||||||
*/
|
*/
|
||||||
_apply() {
|
_apply(...args) {
|
||||||
if (!this._alive) {
|
if (!this._alive) {
|
||||||
console.log('WARN: datepicker not constructed yet [' + this.domId + ']');
|
console.warn('datepicker not constructed yet [%s]', this.domId);
|
||||||
}
|
}
|
||||||
return this._pickerElement.datepicker(...arguments);
|
return this._pickerElement.datepicker(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -267,8 +267,8 @@ export default class RecordingsView {
|
||||||
$('tr.r', tbody).remove();
|
$('tr.r', tbody).remove();
|
||||||
this._recordings.forEach((r) => {
|
this._recordings.forEach((r) => {
|
||||||
let row = $('<tr class="r" />');
|
let row = $('<tr class="r" />');
|
||||||
row.append(_columnOrder.map((k) => $('<td/>')));
|
row.append(_columnOrder.map(() => $('<td/>')));
|
||||||
row.on('click', (e) => {
|
row.on('click', () => {
|
||||||
console.log('Video clicked');
|
console.log('Video clicked');
|
||||||
if (this._clickHandler !== null) {
|
if (this._clickHandler !== null) {
|
||||||
console.log('Video clicked handler call');
|
console.log('Video clicked handler call');
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
import 'jquery-ui/themes/base/core.css';
|
||||||
|
import 'jquery-ui/themes/base/dialog.css';
|
||||||
|
import 'jquery-ui/themes/base/theme.css';
|
||||||
|
// This not needed for pure dialog, but we want it resizable
|
||||||
|
import 'jquery-ui/themes/base/resizable.css';
|
||||||
|
|
||||||
|
// Get dialog ui widget
|
||||||
|
import 'jquery-ui/ui/widgets/dialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to implement a simple jQuery dialog based video player.
|
||||||
|
*/
|
||||||
|
export default class VideoDialogView {
|
||||||
|
/**
|
||||||
|
* Construct the player.
|
||||||
|
*
|
||||||
|
* 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!
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach the player to the specified DOM element.
|
||||||
|
*
|
||||||
|
* @param {Node} domElement DOM element to attach to
|
||||||
|
* @return {VideoDialogView} Returns "this" for chaining.
|
||||||
|
*/
|
||||||
|
attach(domElement) {
|
||||||
|
this._videoElement = $('<video controls preload="auto" autoplay="true" />');
|
||||||
|
this._dialogElement = $('<div class="playdialog" />').append(
|
||||||
|
this._videoElement
|
||||||
|
);
|
||||||
|
$(domElement).append(this._dialogElement);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the player, and start playing the given url.
|
||||||
|
*
|
||||||
|
* @param {String} title Title of the video player
|
||||||
|
* @param {Number} width Width of the player
|
||||||
|
* @param {String} url URL for source media
|
||||||
|
* @return {VideoDialogView} Returns "this" for chaining.
|
||||||
|
*/
|
||||||
|
play(title, width, url) {
|
||||||
|
this._dialogElement.dialog({
|
||||||
|
title: title,
|
||||||
|
width: width,
|
||||||
|
close: () => {
|
||||||
|
const videoDOMElement = this._videoElement[0];
|
||||||
|
videoDOMElement.pause();
|
||||||
|
videoDOMElement.src = ''; // Remove current source to stop loading
|
||||||
|
this._videoElement = null;
|
||||||
|
this._dialogElement.remove();
|
||||||
|
this._dialogElement = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Now that dialog is up, set the src so video starts
|
||||||
|
console.log('Video url: ' + url);
|
||||||
|
this._videoElement.attr('src', url);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,32 +48,41 @@ module.exports = (env, args) => {
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
test: /\.js$/,
|
{
|
||||||
loader: 'babel-loader',
|
test: /\.js$/,
|
||||||
query: {
|
loader: 'babel-loader',
|
||||||
'presets': ['env'],
|
query: {
|
||||||
|
presets: ['env', {modules: false}],
|
||||||
|
},
|
||||||
|
exclude: /(node_modules|bower_components)/,
|
||||||
|
include: ['./ui-src'],
|
||||||
},
|
},
|
||||||
exclude: /(node_modules|bower_components)/,
|
{
|
||||||
include: ['./ui-src'],
|
test: /\.png$/,
|
||||||
}, {
|
use: ['file-loader'],
|
||||||
test: /\.png$/,
|
},
|
||||||
use: ['file-loader'],
|
{
|
||||||
}, {
|
test: /\.ico$/,
|
||||||
test: /\.ico$/,
|
use: [
|
||||||
use: [
|
{
|
||||||
{
|
loader: 'file-loader',
|
||||||
loader: 'file-loader',
|
options: {
|
||||||
options: {
|
name: '[name].[ext]',
|
||||||
name: '[name].[ext]'
|
},
|
||||||
}
|
},
|
||||||
}
|
],
|
||||||
]
|
},
|
||||||
}, {
|
{
|
||||||
// Load css and then in-line in head
|
test: /\.png$/,
|
||||||
test: /\.css$/,
|
use: ['file-loader'],
|
||||||
loader: 'style-loader!css-loader',
|
},
|
||||||
}],
|
{
|
||||||
|
// Load css and then in-line in head
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ['style-loader', 'css-loader'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
|
@ -83,14 +92,20 @@ module.exports = (env, args) => {
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
title: nvrSettings.app_title,
|
title: nvrSettings.app_title,
|
||||||
filename: 'index.html',
|
filename: 'index.html',
|
||||||
template: path.join(nvrSettings._paths.app_src_dir, 'assets', 'index.html'),
|
template: path.join(
|
||||||
|
nvrSettings._paths.app_src_dir,
|
||||||
|
'assets',
|
||||||
|
'index.html'
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
/node_modules\/moment\/moment\.js$/,
|
/node_modules\/moment\/moment\.js$/,
|
||||||
'./min/moment.min.js'),
|
'./min/moment.min.js'
|
||||||
|
),
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
/node_modules\/moment-timezone\/index\.js$/,
|
/node_modules\/moment-timezone\/index\.js$/,
|
||||||
'./builds/moment-timezone-with-data-2012-2022.min.js'),
|
'./builds/moment-timezone-with-data-2012-2022.min.js'
|
||||||
|
),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,6 +44,7 @@ module.exports = (env, args) => {
|
||||||
},
|
},
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
optimization: {
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
namedChunks: true,
|
namedChunks: true,
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
|
@ -54,11 +55,11 @@ module.exports = (env, args) => {
|
||||||
hot: true,
|
hot: true,
|
||||||
clientLogLevel: 'info',
|
clientLogLevel: 'info',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': `http://${nvrSettings.moonfire.server}:${nvrSettings.moonfire.port}`,
|
'/api': `http://${nvrSettings.moonfire.server}:${
|
||||||
|
nvrSettings.moonfire.port
|
||||||
|
}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [new webpack.HotModuleReplacementPlugin()],
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,34 +42,34 @@ const merge = require('webpack-merge');
|
||||||
* found), we throw an exception.
|
* found), we throw an exception.
|
||||||
*
|
*
|
||||||
* If the module that is require-d is a function, it will be executed,
|
* If the module that is require-d is a function, it will be executed,
|
||||||
* passing the "env" and "args" parameters from the settingsConfig to it.
|
* passing the "env" and "args" parameters to it.
|
||||||
* The function should return a map.
|
* The function should return a map.
|
||||||
*
|
*
|
||||||
* @param {String} path Path to be passed to require()
|
* @param {String} requiredPath Path to be passed to require()
|
||||||
* @param {object} settingsConfig Settings passed to new Settings()
|
* @param {object} env webpack's "env" on invocation
|
||||||
|
* @param {object} args webpack's "args" on invocation (options)
|
||||||
* @param {Boolean} optional True file not to exist
|
* @param {Boolean} optional True file not to exist
|
||||||
* @return {object} The module, or {} if not found (optional)
|
* @return {object} The module, or {} if not found (optional)
|
||||||
*/
|
*/
|
||||||
function requireHelper(path, settingsConfig, optional) {
|
function requireHelper(requiredPath, env, args, optional) {
|
||||||
let module = {};
|
let module = {};
|
||||||
try {
|
try {
|
||||||
require.resolve(path); // Throws if not found
|
require.resolve(requiredPath); // Throws if not found
|
||||||
try {
|
try {
|
||||||
module = require(path);
|
module = require(requiredPath);
|
||||||
if (typeof(module) === 'function') {
|
if (typeof module === 'function') {
|
||||||
module = module(settingsConfig.env, settingsConfig.args);
|
module = module(env, args);
|
||||||
}
|
}
|
||||||
// Get owned properties only: now a literal map
|
// Get owned properties only: now a literal map
|
||||||
module = Object.assign({}, require(path).settings);
|
module = Object.assign({}, require(requiredPath).settings);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('Settings file (' + path + ') has errors.');
|
throw new Error('Settings file (' + requiredPath + ') has errors.');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!optional) {
|
if (!optional) {
|
||||||
throw new Error('Settings file (' + path + ') not found.');
|
throw new Error('Settings file (' + requiredPath + ') not found.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const args = settingsConfig.args;
|
|
||||||
const webpackMode = (args ? args.mode : null) || 'none';
|
const webpackMode = (args ? args.mode : null) || 'none';
|
||||||
const modes = module.webpack_mode || {};
|
const modes = module.webpack_mode || {};
|
||||||
delete module.webpack_mode; // Not modifying original module. We have a copy!
|
delete module.webpack_mode; // Not modifying original module. We have a copy!
|
||||||
|
@ -188,13 +188,14 @@ class Settings {
|
||||||
const secondaryPath = path.resolve(projectRoot, secondaryFile);
|
const secondaryPath = path.resolve(projectRoot, secondaryFile);
|
||||||
|
|
||||||
// Check if we can resolve the primary file and if we can, require it.
|
// Check if we can resolve the primary file and if we can, require it.
|
||||||
const _settings =
|
const _settings = requireHelper(primaryPath, env, args, optional);
|
||||||
requireHelper(primaryPath, this.settings_config, optional);
|
|
||||||
|
|
||||||
// Merge secondary override file, if it exists
|
// Merge secondary override file, if it exists
|
||||||
this.settings = merge(_settings,
|
this.settings = merge(
|
||||||
requireHelper(secondaryPath, this.settings_config, true));
|
_settings,
|
||||||
};
|
requireHelper(secondaryPath, env, args, true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take one or more webpack configurations and merge them.
|
* Take one or more webpack configurations and merge them.
|
||||||
|
@ -212,12 +213,14 @@ class Settings {
|
||||||
*/
|
*/
|
||||||
webpackMerge(...packs) {
|
webpackMerge(...packs) {
|
||||||
const unpack = (webpackConfig) => {
|
const unpack = (webpackConfig) => {
|
||||||
if ((typeof(webpackConfig) === 'string') ||
|
if (
|
||||||
(webpackConfig instanceof String)) {
|
typeof webpackConfig === 'string' ||
|
||||||
|
webpackConfig instanceof String
|
||||||
|
) {
|
||||||
webpackConfig = require(webpackConfig);
|
webpackConfig = require(webpackConfig);
|
||||||
}
|
}
|
||||||
const config = this.settings_config;
|
const config = this.settings_config;
|
||||||
if (typeof(webpackConfig) === 'function') {
|
if (typeof webpackConfig === 'function') {
|
||||||
return webpackConfig(config.env, config.args);
|
return webpackConfig(config.env, config.args);
|
||||||
}
|
}
|
||||||
return webpackConfig;
|
return webpackConfig;
|
||||||
|
|
|
@ -42,34 +42,76 @@ module.exports = (env, args) => {
|
||||||
const nvrSettings = settingsObject.settings;
|
const nvrSettings = settingsObject.settings;
|
||||||
|
|
||||||
return settingsObject.webpackMerge(baseConfig, {
|
return settingsObject.webpackMerge(baseConfig, {
|
||||||
//devtool: 'cheap-module-source-map',
|
devtool: 'cheap-module-source-map',
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
test: /\.html$/,
|
{
|
||||||
loader: 'html-loader',
|
test: /\.html$/,
|
||||||
query: {
|
loader: 'html-loader',
|
||||||
minimize: true,
|
query: {
|
||||||
|
minimize: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
minimize: true,
|
minimize: true,
|
||||||
|
minimizer: [
|
||||||
|
{
|
||||||
|
apply: (compiler) => {
|
||||||
|
/**
|
||||||
|
* Setup the UglifyJsPlugin as webpack4 does, plus options
|
||||||
|
* we decide to override.
|
||||||
|
*/
|
||||||
|
// Lazy load the uglifyjs plugin
|
||||||
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
new UglifyJsPlugin({
|
||||||
|
cache: true, // webpack4: default
|
||||||
|
parallel: true, // webpack4: default
|
||||||
|
sourceMap:
|
||||||
|
(args.devtool && /source-?map/.test(args.devtool)) ||
|
||||||
|
(args.plugins &&
|
||||||
|
args.plugins.some(
|
||||||
|
(p) => p instanceof webpack.SourceMapDevToolPlugin
|
||||||
|
)),
|
||||||
|
uglifyOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true, // Remove all console.log etc.
|
||||||
|
keep_infinity: true, // Do not change to 1/0
|
||||||
|
warnings: false, // Do not warn when dropping
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
// Eliminate most comments, but not marked ones
|
||||||
|
comments: 'some',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).apply(compiler);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
minSize: 30000,
|
minSize: 30000,
|
||||||
minChunks: 1,
|
minChunks: 1,
|
||||||
maxAsyncRequests: 5,
|
maxAsyncRequests: 5,
|
||||||
maxInitialRequests: 3,
|
maxInitialRequests: 4,
|
||||||
cacheGroups: {
|
cacheGroups: {
|
||||||
default: {
|
'default': {
|
||||||
minChunks: 2,
|
minChunks: 2,
|
||||||
priority: -20,
|
priority: -20,
|
||||||
},
|
},
|
||||||
commons: {
|
'jquery-ui': {
|
||||||
name: 'commons',
|
test: /[\\/]node_modules[\\/]jquery-ui[\\/]/,
|
||||||
|
name: 'jquery-ui',
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
minChunks: 2,
|
priority: -5,
|
||||||
},
|
},
|
||||||
vendors: {
|
'jquery': {
|
||||||
|
test: /[\\/]node_modules[\\/]jquery[\\/]/,
|
||||||
|
name: 'jquery',
|
||||||
|
chunks: 'all',
|
||||||
|
priority: -5,
|
||||||
|
},
|
||||||
|
'vendors': {
|
||||||
test: /[\\/]node_modules[\\/]/,
|
test: /[\\/]node_modules[\\/]/,
|
||||||
name: 'vendor',
|
name: 'vendor',
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
|
@ -89,9 +131,6 @@ module.exports = (env, args) => {
|
||||||
threshold: 10240,
|
threshold: 10240,
|
||||||
minRatio: 0.8,
|
minRatio: 0.8,
|
||||||
}),
|
}),
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
|
||||||
/node_modules\/jquery\/dist\/jquery\.js$/,
|
|
||||||
'./jquery.min.js'),
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
37
yarn.lock
37
yarn.lock
|
@ -865,6 +865,17 @@ babel-plugin-transform-flow-strip-types@^6.8.0:
|
||||||
babel-plugin-syntax-flow "^6.18.0"
|
babel-plugin-syntax-flow "^6.18.0"
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.22.0"
|
||||||
|
|
||||||
|
babel-plugin-transform-imports@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-transform-imports/-/babel-plugin-transform-imports-1.5.0.tgz#3105082ab489b1cee162e42d2ffe7b8f7c685f2e"
|
||||||
|
dependencies:
|
||||||
|
babel-types "^6.6.0"
|
||||||
|
is-valid-path "^0.1.1"
|
||||||
|
lodash.camelcase "^4.3.0"
|
||||||
|
lodash.findkey "^4.6.0"
|
||||||
|
lodash.kebabcase "^4.1.1"
|
||||||
|
lodash.snakecase "^4.1.1"
|
||||||
|
|
||||||
babel-plugin-transform-inline-consecutive-adds@^0.3.0:
|
babel-plugin-transform-inline-consecutive-adds@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.3.0.tgz#f07d93689c0002ed2b2b62969bdd99f734e03f57"
|
resolved "https://registry.yarnpkg.com/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.3.0.tgz#f07d93689c0002ed2b2b62969bdd99f734e03f57"
|
||||||
|
@ -1095,7 +1106,7 @@ babel-traverse@^6.24.1, babel-traverse@^6.26.0:
|
||||||
invariant "^2.2.2"
|
invariant "^2.2.2"
|
||||||
lodash "^4.17.4"
|
lodash "^4.17.4"
|
||||||
|
|
||||||
babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0:
|
babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0, babel-types@^6.6.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
|
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3625,6 +3636,12 @@ is-glob@^4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-extglob "^2.1.1"
|
is-extglob "^2.1.1"
|
||||||
|
|
||||||
|
is-invalid-path@^0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34"
|
||||||
|
dependencies:
|
||||||
|
is-glob "^2.0.0"
|
||||||
|
|
||||||
is-number@^2.1.0:
|
is-number@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
|
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
|
||||||
|
@ -3737,6 +3754,12 @@ is-utf8@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||||
|
|
||||||
|
is-valid-path@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df"
|
||||||
|
dependencies:
|
||||||
|
is-invalid-path "^0.1.0"
|
||||||
|
|
||||||
is-windows@^1.0.1, is-windows@^1.0.2:
|
is-windows@^1.0.1, is-windows@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||||
|
@ -4043,14 +4066,26 @@ lodash.camelcase@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||||
|
|
||||||
|
lodash.findkey@^4.6.0:
|
||||||
|
version "4.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718"
|
||||||
|
|
||||||
lodash.isplainobject@^4.0.6:
|
lodash.isplainobject@^4.0.6:
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||||
|
|
||||||
|
lodash.kebabcase@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
|
||||||
|
|
||||||
lodash.memoize@^4.1.2:
|
lodash.memoize@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
|
|
||||||
|
lodash.snakecase@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
|
||||||
|
|
||||||
lodash.some@^4.6.0:
|
lodash.some@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||||
|
|
Loading…
Reference in New Issue