Merge remote-tracking branch 'balloob/master'

This commit is contained in:
Ryan Kraus 2016-01-07 20:59:02 -05:00
commit 97d4514dc6
68 changed files with 748 additions and 302 deletions

View File

@ -1,10 +1,6 @@
{
"stage": 1,
"highlightCode": true,
"optional": ["es7.objectRestSpread", "es7.classProperties"],
"env": {
"production": {
"optional": ["optimisation"]
}
}
"presets": ["es2015"],
"plugins": ["transform-object-rest-spread", "transform-export-extensions",
"transform-class-properties", "babel-plugin-default-import-checker"],
"highlightCode": true
}

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
4.2.3

View File

@ -8,10 +8,11 @@
"license": "MIT",
"private": true,
"devDependencies": {
"polymer": "Polymer/polymer#^1.2",
"polymer": "Polymer/polymer#~1.2.3",
"pikaday": "1.4",
"leaflet-map": "1.1.0",
"iron-elements": "PolymerElements/iron-elements#~1.0.4",
"paper-elements": "PolymerElements/paper-elements#~1.0.6"
"iron-elements": "PolymerElements/iron-elements#~1.0.7",
"paper-elements": "PolymerElements/paper-elements#~1.0.7",
"google-apis": "GoogleWebComponents/google-apis#~1.1.3"
}
}

View File

@ -20,19 +20,24 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "MIT",
"dependencies": {
"home-assistant-js": "git+https://github.com/balloob/home-assistant-js.git#2877f375e76c85505de34af6b752478664570c23",
"home-assistant-js": "git+https://github.com/balloob/home-assistant-js.git#b35fb90c94c1dfbb45c9691e9789f0b84b949d94",
"lodash": "^3.10.1",
"moment": "^2.10.6"
},
"devDependencies": {
"babel-core": "^5.8.29",
"babel-eslint": "^4.1.5",
"babel-loader": "^5.3.2",
"babel-core": "^6.3",
"babel-eslint": "^5.0.0-beta6",
"babel-loader": "^6.2",
"babel-plugin-default-import-checker": "^1.0.8",
"babel-plugin-transform-class-properties": "^6.3.13",
"babel-plugin-transform-export-extensions": "^6.3.13",
"babel-plugin-transform-object-rest-spread": "^6.3.13",
"babel-preset-es2015": "^6.0.0",
"bower": "^1.6.5",
"eslint": "^1.9",
"eslint-config-airbnb": "^1.0.0",
"eslint": "^1.10.3",
"eslint-config-airbnb": "^0.1.0",
"html-minifier": "^1.0.0",
"vulcanize": "^1.14.0",
"webpack": "^1.12.2"
"webpack": "^1.12"
}
}

View File

@ -1,11 +1,13 @@
import Polymer from '../polymer';
import { moreInfoActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import canToggle from '../util/can-toggle';
require('../components/ha-card');
require('../components/entity/ha-entity-toggle');
require('../state-summary/state-card-content');
const { moreInfoActions } = hass;
export default new Polymer({
is: 'ha-domain-card',
@ -36,7 +38,7 @@ export default new Polymer({
},
showGroupToggle(groupEntity, states) {
if (!groupEntity || groupEntity.state !== 'on' && groupEntity.state !== 'off') {
if (!groupEntity || !states || groupEntity.state !== 'on' && groupEntity.state !== 'off') {
return false;
}

View File

@ -49,7 +49,7 @@
<li><a href='https://home-assistant.io/components/' target='_blank'>
Available components
</a></li>
<li><a href='https://home-assistant.io/getting-started/troubleshooting-configuration/'>Troubleshooting your configuration</a></li>
<li><a href='https://home-assistant.io/getting-started/troubleshooting-configuration/' target='_blank'>Troubleshooting your configuration</a></li>
<li><a href='https://home-assistant.io/help/' target='_blank'>
Getting help
</a></li>

View File

@ -1,8 +1,10 @@
import { entityGetters } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
const { entityGetters } = hass;
export default new Polymer({
is: 'entity-list',

View File

@ -1,11 +1,13 @@
import Polymer from '../../polymer';
import {
import hass from '../../util/home-assistant-js-instance';
require('../../components/ha-label-badge');
const {
reactor,
entityGetters,
moreInfoActions,
} from '../../util/home-assistant-js-instance';
require('../../components/ha-label-badge');
} = hass;
export default new Polymer({
is: 'ha-entity-marker',

View File

@ -5,7 +5,7 @@
<style>
paper-toggle-button {
cursor: pointer;
margin-right: -14px;
--paper-toggle-button-label-spacing: 0;
}
</style>
<template>

View File

@ -1,7 +1,9 @@
import { serviceActions } from '../../util/home-assistant-js-instance';
import hass from '../../util/home-assistant-js-instance';
import Polymer from '../../polymer';
const { serviceActions } = hass;
export default new Polymer({
is: 'ha-entity-toggle',
@ -23,11 +25,12 @@ export default new Polymer({
toggleChanged(ev) {
const newVal = ev.target.checked;
const curVal = this._checkToggle(this.stateObj);
if (newVal && this.stateObj.state === 'off') {
this.turn_on();
} else if (!newVal && this.stateObj.state !== 'off') {
this.turn_off();
if (newVal && !curVal) {
this._call_service(true);
} else if (!newVal && curVal) {
this._call_service(false);
}
},
@ -49,23 +52,27 @@ export default new Polymer({
this.toggleChecked = newState;
},
turn_on() {
// We call updateToggle after a successful call to re-sync the toggle
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
serviceActions.callTurnOn(this.stateObj.entityId).then(() => this.forceStateChange());
},
turn_off() {
// We call updateToggle after a successful call to re-sync the toggle
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
serviceActions.callTurnOff(this.stateObj.entityId).then(() => this.forceStateChange());
},
_checkToggle(stateObj) {
return stateObj && stateObj.state !== 'off';
return stateObj && stateObj.state !== 'off' && stateObj.state !== 'unlocked';
},
// We call updateToggle after a successful call to re-sync the toggle
// with the state. It will be out of sync if our service call did not
// result in the entity to be turned on. Since the state is not changing,
// the resync is not called automatic.
_call_service(turnOn) {
let domain;
let service;
if (this.stateObj.domain === 'lock') {
domain = 'lock';
service = turnOn ? 'lock' : 'unlock';
} else {
domain = 'homeassistant';
service = turnOn ? 'turn_on' : 'turn_off';
}
serviceActions.callService(domain, service, {entity_id: this.stateObj.entityId})
.then(() => this.forceStateChange());
},
});

View File

@ -1,13 +1,15 @@
import Polymer from '../../polymer';
import {
moreInfoActions,
serviceActions,
} from '../../util/home-assistant-js-instance';
import hass from '../../util/home-assistant-js-instance';
import domainIcon from '../../util/domain-icon';
import canToggle from '../../util/can-toggle';
require('../../components/ha-label-badge');
const {
moreInfoActions,
serviceActions,
} = hass;
export default new Polymer({
is: 'ha-state-label-badge',
@ -41,6 +43,7 @@ export default new Polymer({
switch (state.domain) {
case 'scene':
return 'green';
case 'binary_sensor':
case 'script':
return state.state === 'on' ? 'blue' : 'grey';
case 'updater':
@ -52,6 +55,7 @@ export default new Polymer({
computeValue(state) {
switch (state.domain) {
case 'binary_sensor':
case 'device_tracker':
case 'updater':
case 'sun':
@ -77,13 +81,13 @@ export default new Polymer({
return 'mdi:home-variant';
}
// state == 'disarmed'
return 'mdi:lock-open';
return domainIcon(state.domain, state.state);
case 'binary_sensor':
case 'device_tracker':
case 'scene':
case 'updater':
case 'script':
return domainIcon(state.domain, state.state);
case 'updater':
return domainIcon(state.domain);
case 'sun':
return state.state === 'above_horizon' ?
domainIcon(state.domain) : 'mdi:brightness-3';

View File

@ -1,8 +1,10 @@
import { eventGetters } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
const { eventGetters } = hass;
export default new Polymer({
is: 'events-list',

View File

@ -7,35 +7,21 @@
*/
import Polymer from '../polymer';
/**
* given red, green, blue values, return the equivalent hexidecimal value
* base source: http://stackoverflow.com/a/5624139
*/
function componentToHex(comp) {
const hex = comp.toString(16);
return hex.length === 1 ? '0' + hex : hex;
}
function rgbToHex(color) {
return '#' + componentToHex(color.r) + componentToHex(color.g) +
componentToHex(color.b);
}
export default new Polymer({
is: 'ha-color-picker',
properties: {
width: {
type: Number,
value: 300,
},
height: {
type: Number,
value: 300,
},
color: {
type: Object,
},
width: {
type: Number,
},
height: {
type: Number,
},
},
listeners: {
@ -64,56 +50,45 @@ export default new Polymer({
},
onTouchMove(ev) {
const touch = ev.touches[0];
this.onColorSelect(ev, {x: touch.clientX, y: touch.clientY});
if (!this.mouseMoveIsThrottled) {
return;
}
this.mouseMoveIsThrottled = false;
this.processColorSelect(ev.touches[0]);
this.async(() => this.mouseMoveIsThrottled = true, 100);
},
onMouseMove(ev) {
ev.preventDefault();
if (this.mouseMoveIsThrottled) {
this.mouseMoveIsThrottled = false;
this.onColorSelect(ev);
this.async(() => this.mouseMoveIsThrottled = true, 100);
if (!this.mouseMoveIsThrottled) {
return;
}
this.mouseMoveIsThrottled = false;
this.processColorSelect(ev);
this.async(() => this.mouseMoveIsThrottled = true, 100);
},
onColorSelect(ev, coords) {
if (this.context) {
const colorCoords = coords || this.relativeMouseCoordinates(ev);
const data = this.context.getImageData(colorCoords.x, colorCoords.y, 1, 1).data;
processColorSelect(ev) {
const rect = this.canvas.getBoundingClientRect();
this.setColor({r: data[0], g: data[1], b: data[2]});
// boundary check because people can move off-canvas.
if (ev.clientX < rect.left || ev.clientX >= rect.left + rect.width ||
ev.clientY < rect.top || ev.clientY >= rect.top + rect.height) {
return;
}
this.onColorSelect(ev.clientX - rect.left, ev.clientY - rect.top);
},
onColorSelect(x, y) {
const data = this.context.getImageData(x, y, 1, 1).data;
this.setColor({r: data[0], g: data[1], b: data[2]});
},
setColor(rgb) {
// save calculated color
this.color = {hex: rgbToHex(rgb), rgb: rgb};
this.color = rgb;
this.fire('colorselected', {
rgb: this.color.rgb,
hex: this.color.hex,
});
},
/**
* given a mouse click event, return x,y coordinates relative to the clicked target
* @returns object with x, y values
*/
relativeMouseCoordinates(ev) {
let xCoord = 0;
let yCoord = 0;
if (this.canvas) {
const rect = this.canvas.getBoundingClientRect();
xCoord = ev.clientX - rect.left;
yCoord = ev.clientY - rect.top;
}
return {
x: xCoord,
y: yCoord,
};
this.fire('colorselected', {rgb: this.color});
},
ready() {
@ -122,25 +97,34 @@ export default new Polymer({
this.canvas = this.children[0];
this.context = this.canvas.getContext('2d');
const colorGradient = this.context.createLinearGradient(0, 0, this.width, 0);
colorGradient.addColorStop(0, 'rgb(255,0,0)');
colorGradient.addColorStop(0.16, 'rgb(255,0,255)');
colorGradient.addColorStop(0.32, 'rgb(0,0,255)');
colorGradient.addColorStop(0.48, 'rgb(0,255,255)');
colorGradient.addColorStop(0.64, 'rgb(0,255,0)');
colorGradient.addColorStop(0.80, 'rgb(255,255,0)');
colorGradient.addColorStop(1, 'rgb(255,0,0)');
this.context.fillStyle = colorGradient;
this.context.fillRect(0, 0, this.width, this.height);
this.debounce('drawGradient', () => {
const style = getComputedStyle(this);
const width = parseInt(style.width, 10);
const height = parseInt(style.height, 10);
const bwGradient = this.context.createLinearGradient(0, 0, 0, this.height);
bwGradient.addColorStop(0, 'rgba(255,255,255,1)');
bwGradient.addColorStop(0.5, 'rgba(255,255,255,0)');
bwGradient.addColorStop(0.5, 'rgba(0,0,0,0)');
bwGradient.addColorStop(1, 'rgba(0,0,0,1)');
this.width = width;
this.height = height;
this.context.fillStyle = bwGradient;
this.context.fillRect(0, 0, this.width, this.height);
const colorGradient = this.context.createLinearGradient(0, 0, width, 0);
colorGradient.addColorStop(0, 'rgb(255,0,0)');
colorGradient.addColorStop(0.16, 'rgb(255,0,255)');
colorGradient.addColorStop(0.32, 'rgb(0,0,255)');
colorGradient.addColorStop(0.48, 'rgb(0,255,255)');
colorGradient.addColorStop(0.64, 'rgb(0,255,0)');
colorGradient.addColorStop(0.80, 'rgb(255,255,0)');
colorGradient.addColorStop(1, 'rgb(255,0,0)');
this.context.fillStyle = colorGradient;
this.context.fillRect(0, 0, width, height);
const bwGradient = this.context.createLinearGradient(0, 0, 0, height);
bwGradient.addColorStop(0, 'rgba(255,255,255,1)');
bwGradient.addColorStop(0.5, 'rgba(255,255,255,0)');
bwGradient.addColorStop(0.5, 'rgba(0,0,0,0)');
bwGradient.addColorStop(1, 'rgba(0,0,0,1)');
this.context.fillStyle = bwGradient;
this.context.fillRect(0, 0, width, height);
}, 100);
},
});

View File

@ -113,6 +113,9 @@
<paper-icon-button
icon='mdi:nfc' data-panel='devEvent'
on-click='handleDevClick'></paper-icon-button>
<paper-icon-button
icon='mdi:file-xml' data-panel='devTemplate'
on-click='handleDevClick'></paper-icon-button>
<paper-icon-button
icon='mdi:information-outline' data-panel='devInfo'
on-click='handleDevClick'></paper-icon-button>

View File

@ -1,15 +1,17 @@
import {
configGetters,
navigationGetters,
authActions,
navigationActions,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('./stream-status');
const {
configGetters,
navigationGetters,
authActions,
navigationActions,
} = hass;
export default new Polymer({
is: 'ha-sidebar',

View File

@ -25,12 +25,30 @@
.column {
max-width: 500px;
overflow-x: hidden;
}
.zone-card {
margin-left: 8px;
margin-bottom: 8px;
}
@media (max-width: 500px) {
:host {
padding-right: 0;
}
.zone-card {
margin-left: 0;
}
}
@media (max-width: 599px) {
.column {
max-width: 600px;
}
}
</style>
<template>

View File

@ -1,11 +1,12 @@
import Polymer from '../polymer';
import { util } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
require('.//ha-demo-badge');
require('../cards/ha-badges-card');
require('../cards/ha-domain-card');
require('../cards/ha-introduction-card');
const { util } = hass;
const PRIORITY = {
configurator: -20,
group: -10,
@ -16,10 +17,9 @@ const PRIORITY = {
alarm_control_panel: 3,
camera: 4,
sensor: 5,
scene: 6,
script: 7,
thermostat: 40,
media_player: 50,
binary_sensor: 6,
scene: 7,
script: 8,
};
function getPriority(domain) {

View File

@ -1,4 +1,4 @@
import { moreInfoActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
@ -6,6 +6,8 @@ require('./domain-icon');
require('./display-time');
require('./relative-ha-datetime');
const { moreInfoActions } = hass;
export default new Polymer({
is: 'logbook-entry',

View File

@ -1,10 +1,10 @@
import moment from 'moment';
import { util } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
const UPDATE_INTERVAL = 60000; // 60 seconds
const { parseDateTime } = util;
const { util: { parseDateTime } } = hass;
export default new Polymer({
is: 'relative-ha-datetime',

View File

@ -1,10 +1,12 @@
import { serviceGetters } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('./domain-icon');
const { serviceGetters } = hass;
export default new Polymer({
is: 'services-list',

View File

@ -1,8 +1,10 @@
import { streamGetters, streamActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
const { streamGetters, streamActions } = hass;
export default new Polymer({
is: 'stream-status',

View File

@ -1,11 +1,13 @@
import {
voiceActions,
voiceGetters,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
const {
voiceActions,
voiceGetters,
} = hass;
export default new Polymer({
is: 'ha-voice-command-dialog',

View File

@ -1,10 +1,4 @@
import {
configGetters,
entityHistoryGetters,
entityHistoryActions,
moreInfoGetters,
moreInfoActions,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
@ -13,6 +7,14 @@ require('../state-summary/state-card-content');
require('../components/state-history-charts');
require('../more-infos/more-info-content');
const {
configGetters,
entityHistoryGetters,
entityHistoryActions,
moreInfoGetters,
moreInfoActions,
} = hass;
// if you don't want the history component to show add the domain to this array
const DOMAINS_WITH_NO_HISTORY = ['camera', 'configurator', 'scene'];
@ -73,13 +75,6 @@ export default new Polymer({
type: Boolean,
value: false,
},
_boundOnBackdropTap: {
type: Function,
value: function bindBackdropTap() {
return this._onBackdropTap.bind(this);
},
},
},
/**
@ -121,18 +116,10 @@ export default new Polymer({
dialogOpenChanged(newVal) {
if (newVal) {
this.$.dialog.backdropElement.addEventListener('click',
this._boundOnBackdropTap);
this.async(() => this._delayedDialogOpen = true, 10);
} else if (!newVal && this.stateObj) {
moreInfoActions.deselectEntity();
this._delayedDialogOpen = false;
}
},
_onBackdropTap() {
this.$.dialog.backdropElement.removeEventListener('click',
this._boundOnBackdropTap);
this.dialogOpen = false;
},
});

View File

@ -1,12 +1,6 @@
import Polymer from './polymer';
import {
localStoragePreferences,
navigationActions,
reactor,
startLocalStoragePreferencesSync,
syncGetters,
} from './util/home-assistant-js-instance';
import hass from './util/home-assistant-js-instance';
import nuclearObserver from './util/bound-nuclear-behavior';
import validateAuth from './util/validate-auth';
@ -14,6 +8,14 @@ import validateAuth from './util/validate-auth';
require('./layouts/login-form');
require('./layouts/home-assistant-main');
const {
localStoragePreferences,
navigationActions,
reactor,
startLocalStoragePreferencesSync,
syncGetters,
} = hass;
export default new Polymer({
is: 'home-assistant',
@ -54,9 +56,16 @@ export default new Polymer({
},
loadIcons() {
// If the import fails, we'll try to import again, must be a server glitch
// Since HTML imports only resolve once, we import another url.
const success = () => this.iconsLoaded = true;
this.importHref(`/static/mdi-${this.icons}.html`,
() => this.iconsLoaded = true,
() => this.loadIcons());
success,
() => this.importHref(`/static/mdi.html`, success, success));
},
created() {
this.registerServiceWorker();
},
ready() {
@ -72,10 +81,14 @@ export default new Polymer({
startLocalStoragePreferencesSync();
// remove the HTML init message
const initMsg = document.getElementById('init');
initMsg.parentElement.removeChild(initMsg);
this.loadIcons();
},
registerServiceWorker() {
if (!('serviceWorker' in navigator)) {
return;
}
navigator.serviceWorker.register('./service_worker.js');
},
});

View File

@ -8,6 +8,7 @@
<link rel='import' href='../layouts/partial-dev-call-service.html'>
<link rel='import' href='../layouts/partial-dev-fire-event.html'>
<link rel='import' href='../layouts/partial-dev-set-state.html'>
<link rel='import' href='../layouts/partial-dev-template.html'>
<link rel='import' href='../layouts/partial-dev-info.html'>
<link rel='import' href='../managers/notification-manager.html'>
<link rel="import" href="../dialogs/more-info-dialog.html">
@ -50,6 +51,9 @@
<template is='dom-if' if='[[isSelectedDevState]]'>
<partial-dev-set-state main narrow='[[narrow]]' show-menu='[[showSidebar]]'></partial-dev-set-state>
</template>
<template is='dom-if' if='[[isSelectedDevTemplate]]'>
<partial-dev-template main narrow='[[narrow]]' show-menu='[[showSidebar]]'></partial-dev-template>
</template>
<template is='dom-if' if='[[isSelectedDevInfo]]'>
<partial-dev-info main narrow='[[narrow]]' show-menu='[[showSidebar]]'></partial-dev-info>
</template>

View File

@ -1,12 +1,8 @@
import Polymer from '../polymer';
import {
navigationActions,
navigationGetters,
startUrlSync,
stopUrlSync,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import nuclearObserver from '../util/bound-nuclear-behavior';
import removeInitMsg from '../util/remove-init-message';
require('../components/ha-sidebar');
require('../layouts/partial-zone');
@ -16,11 +12,19 @@ require('../layouts/partial-map');
require('../layouts/partial-dev-call-service');
require('../layouts/partial-dev-fire-event');
require('../layouts/partial-dev-set-state');
require('../layouts/partial-dev-template');
require('../layouts/partial-dev-info');
require('../managers/notification-manager');
require('../dialogs/more-info-dialog');
require('../dialogs/ha-voice-command-dialog');
const {
navigationActions,
navigationGetters,
startUrlSync,
stopUrlSync,
} = hass;
export default new Polymer({
is: 'home-assistant-main',
@ -68,6 +72,11 @@ export default new Polymer({
bindNuclear: navigationGetters.isActivePane('devState'),
},
isSelectedDevTemplate: {
type: Boolean,
bindNuclear: navigationGetters.isActivePane('devTemplate'),
},
isSelectedDevService: {
type: Boolean,
bindNuclear: navigationGetters.isActivePane('devService'),
@ -111,6 +120,7 @@ export default new Polymer({
},
attached() {
removeInitMsg();
startUrlSync();
},

View File

@ -51,7 +51,7 @@
<template>
<div class="layout vertical center center-center fit">
<img src="/static/splash.png" height="230" />
<img src="/static/favicon-192x192.png" height="192" />
<a href="#" id="hideKeyboardOnFocus"></a>
<div class='interact'>
<div id='loginform' hidden$="[[showLoading]]">

View File

@ -1,9 +1,12 @@
import Polymer from '../polymer';
import { authGetters } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import nuclearObserver from '../util/bound-nuclear-behavior';
import validateAuth from '../util/validate-auth';
import removeInitMsg from '../util/remove-init-message';
const { authGetters } = hass;
export default new Polymer({
is: 'login-form',
@ -52,6 +55,10 @@ export default new Polymer({
'validatingChanged(isValidating, isInvalid)',
],
attached() {
removeInitMsg();
},
computeShowSpinner(forceShowLoading, isValidating) {
return forceShowLoading || isValidating;
},

View File

@ -1,14 +1,16 @@
import {
reactor,
serviceActions,
serviceGetters,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
require('./partial-base');
require('../components/services-list');
const {
reactor,
serviceActions,
serviceGetters,
} = hass;
export default new Polymer({
is: 'partial-dev-call-service',

View File

@ -1,10 +1,12 @@
import { eventActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
require('./partial-base');
require('../components/events-list');
const { eventActions } = hass;
export default new Polymer({
is: 'partial-dev-fire-event',

View File

@ -4,8 +4,6 @@
<link rel="import" href="./partial-base.html">
<link rel="import" href="../components/services-list.html">
<dom-module id="partial-dev-info">
<style>
.content {
@ -56,7 +54,8 @@
<div class='content fit'>
<div class='about'>
<p class='version'>
<a href='https://home-assistant.io'><img src="/static/splash.png" height="230" /></a><br />
<a href='https://home-assistant.io'><img src="/static/favicon-192x192.png" height="192" /></a><br />
Home Assistant<br />
[[hassVersion]]
</p>
<p class='develop'>

View File

@ -1,13 +1,15 @@
import {
configGetters,
errorLogActions,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('./partial-base');
const {
configGetters,
errorLogActions,
} = hass;
export default new Polymer({
is: 'partial-dev-info',

View File

@ -1,10 +1,12 @@
import { reactor, entityGetters, entityActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
require('./partial-base');
require('../components/entity-list');
const { reactor, entityGetters, entityActions } = hass;
export default new Polymer({
is: 'partial-dev-set-state',

View File

@ -0,0 +1,71 @@
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/paper-input/paper-textarea.html">
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
<link rel="import" href="./partial-base.html">
<link rel="import" href="../components/services-list.html">
<dom-module id="partial-dev-template">
<style>
.content {
margin-top: 64px;
padding: 16px;
background-color: white;
-ms-user-select: initial;
-webkit-user-select: initial;
-moz-user-select: initial;
}
.edit-pane {
margin-right: 16px;
}
.horizontal .edit-pane {
max-width: 50%;
}
.render-pane {
position: relative;
}
.render-spinner {
position: absolute;
top: 8px;
right: 8px;
}
.rendered {
clear: both;
white-space: pre-wrap;
}
.rendered.error {
color: red;
}
</style>
<template>
<partial-base narrow="[[narrow]]" show-menu='[[showMenu]]'>
<span header-title>Template Editor</span>
<div class$='[[computeFormClasses(narrow)]]'>
<div class='edit-pane'>
<p>
Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.
</p>
<ul>
<li><a href='http://jinja.pocoo.org/docs/dev/templates/' target='_blank'>Jinja2 tempate documentation</a></li>
<li><a href='https://home-assistant.io/getting-started/templating/' target='_blank'>Home Assistant template extensions</a></li>
</ul>
</p>
<paper-textarea label="Template" value='{{template}}'></paper-textarea>
</div>
<div class='render-pane'>
<paper-spinner class='render-spinner' active='[[rendering]]'></paper-spinner>
<pre class$='[[computeRenderedClasses(error)]]'>[[processed]]</pre>
</div>
</div>
</partial-base>
</template>
</dom-module>

View File

@ -0,0 +1,94 @@
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('./partial-base');
const {
templateActions,
} = hass;
export default new Polymer({
is: 'partial-dev-template',
behaviors: [nuclearObserver],
properties: {
narrow: {
type: Boolean,
value: false,
},
showMenu: {
type: Boolean,
value: false,
},
error: {
type: Boolean,
value: false,
},
rendering: {
type: Boolean,
value: false,
},
template: {
type: String,
value: '{%- if is_state("device_tracker.paulus", "home") and \n' +
' is_state("device_tracker.anne_therese", "home") -%}\n' +
'\n' +
' You are both home, you silly\n' +
'\n' +
'{%- else -%}\n' +
'\n' +
' Anne Therese is at {{ states("device_tracker.anne_therese") }} and ' +
'Paulus is at {{ states("device_tracker.paulus") }}\n' +
'\n' +
'{%- endif %}\n' +
'\n' +
'For loop example:\n' +
'\n' +
'{% for state in states.sensor -%}\n' +
' {%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}\n' +
' {{ state.name | lower }} is {{state.state}} {{- state.attributes.unit_of_measurement}}\n' +
'{%- endfor -%}.',
observer: 'templateChanged',
},
processed: {
type: String,
value: '',
},
},
computeFormClasses(narrow) {
return 'content fit layout ' + (narrow ? 'vertical' : 'horizontal');
},
computeRenderedClasses(error) {
return error ? 'error rendered' : 'rendered';
},
templateChanged() {
if (this.error) {
this.error = false;
}
this.debounce('render-template', this.renderTemplate, 500);
},
renderTemplate() {
this.rendering = true;
templateActions.render(this.template).then(processed => {
this.processed = processed;
this.rendering = false;
}, error => {
this.processed = error.message;
this.error = true;
this.rendering = false;
});
},
});

View File

@ -1,7 +1,4 @@
import {
entityHistoryGetters,
entityHistoryActions,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
@ -9,6 +6,11 @@ import nuclearObserver from '../util/bound-nuclear-behavior';
require('./partial-base');
require('../components/state-history-charts');
const {
entityHistoryGetters,
entityHistoryActions,
} = hass;
export default new Polymer({
is: 'partial-history',

View File

@ -1,4 +1,4 @@
import { logbookGetters, logbookActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
@ -7,6 +7,8 @@ require('./partial-base');
require('../components/ha-logbook');
require('../components/loading-box');
const { logbookGetters, logbookActions } = hass;
export default new Polymer({
is: 'partial-logbook',

View File

@ -1,13 +1,15 @@
import {
configGetters,
entityGetters,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('../components/entity/ha-entity-marker');
const {
configGetters,
entityGetters,
} = hass;
window.L.Icon.Default.imagePath = '/static/images/leaflet';
export default new Polymer({

View File

@ -31,7 +31,7 @@
on-click="handleListenClick"></paper-icon-button>
</paper-toolbar>
<div class='content fit'>
<div class='fit'>
<ha-zone-cards
show-introduction='[[computeShowIntroduction(introductionLoaded, states)]]'
states='[[states]]' columns='[[columns]]'></ha-zone-cards>

View File

@ -1,4 +1,12 @@
import {
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('./partial-base');
require('../components/ha-zone-cards');
const {
configGetters,
entityGetters,
voiceGetters,
@ -6,13 +14,7 @@ import {
syncGetters,
syncActions,
voiceActions,
} from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('./partial-base');
require('../components/ha-zone-cards');
} = hass;
export default new Polymer({
is: 'partial-zone',
@ -67,6 +69,7 @@ export default new Polymer({
columns: {
type: Number,
value: 1,
},
},
@ -74,7 +77,7 @@ export default new Polymer({
this.windowChange = this.windowChange.bind(this);
const sizes = [];
for (let col = 0; col < 5; col++) {
sizes.push(278 + col * 278);
sizes.push(300 + col * 300);
}
this.mqls = sizes.map(width => {
const mql = window.matchMedia(`(min-width: ${width}px)`);

View File

@ -1,8 +1,10 @@
import { notificationGetters } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
const { notificationGetters } = hass;
export default new Polymer({
is: 'notification-manager',

View File

@ -1,7 +1,9 @@
import { serviceActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
const { serviceActions } = hass;
export default new Polymer({
is: 'more-info-alarm_control_panel',
handleDisarmTap() {

View File

@ -1,14 +1,16 @@
import {
streamGetters,
syncActions,
serviceActions,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('../components/loading-box');
const {
streamGetters,
syncActions,
serviceActions,
} = hass;
export default new Polymer({
is: 'more-info-configurator',

View File

@ -1,6 +1,6 @@
import Polymer from '../polymer';
const FILTER_KEYS = ['entity_picture', 'friendly_name', 'unit_of_measurement'];
const FILTER_KEYS = ['entity_picture', 'friendly_name', 'icon', 'unit_of_measurement'];
export default new Polymer({
is: 'more-info-default',

View File

@ -1,13 +1,15 @@
import {
entityGetters,
moreInfoGetters,
} from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import nuclearObserver from '../util/bound-nuclear-behavior';
require('../state-summary/state-card-content');
const {
entityGetters,
moreInfoGetters,
} = hass;
export default new Polymer({
is: 'more-info-group',

View File

@ -15,7 +15,8 @@
ha-color-picker {
display: block;
width: 350px;
max-width: 350px;
height: 200px;
margin: 0 auto;
max-height: 0px;
@ -52,7 +53,7 @@
</paper-slider>
</div>
<ha-color-picker on-colorselected='colorPicked' width='350' height='200'>
<ha-color-picker on-colorselected='colorPicked'>
</ha-color-picker>
</div>
</template>

View File

@ -1,12 +1,20 @@
import { serviceActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
require('../components/ha-color-picker');
const { serviceActions } = hass;
const ATTRIBUTE_CLASSES = ['brightness', 'rgb_color', 'color_temp'];
function pickColor(entityId, color) {
serviceActions.callService('light', 'turn_on', {
entity_id: entityId,
rgb_color: [color.r, color.g, color.b],
});
}
export default new Polymer({
is: 'more-info-light',
@ -66,13 +74,29 @@ export default new Polymer({
});
},
/**
* Called when a new color has been picked. We will not respond to every
* color pick event but have a pause between requests.
*/
colorPicked(ev) {
const color = ev.detail.rgb;
if (this.skipColorPicked) {
this.colorChanged = true;
return;
}
serviceActions.callService('light', 'turn_on', {
entity_id: this.stateObj.entityId,
rgb_color: [color.r, color.g, color.b],
});
this.color = ev.detail.rgb;
pickColor(this.stateObj.entityId, this.color);
this.colorChanged = false;
this.skipColorPicked = true;
this.colorDebounce = setTimeout(() => {
if (this.colorChanged) {
pickColor(this.stateObj.entityId, this.color);
}
this.skipColorPicked = false;
}, 500);
},
});

View File

@ -1,8 +1,9 @@
import { serviceActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
const { serviceActions } = hass;
const ATTRIBUTE_CLASSES = ['volume_level'];
export default new Polymer({

View File

@ -1,9 +1,9 @@
import Polymer from '../polymer';
import { util } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import formatTime from '../util/format-time';
const { parseDateTime } = util;
const { util: { parseDateTime } } = hass;
export default new Polymer({
is: 'more-info-sun',

View File

@ -22,7 +22,7 @@
<div>
<div>Target Temperature</div>
<paper-slider
min='[[tempMin]]' max='[[tempMax]]'
min='[[tempMin]]' max='[[tempMax]]' step='0.5'
value='[[targetTemperatureSliderValue]]' pin
on-change='targetTemperatureSliderChanged'>
</paper-slider>

View File

@ -1,8 +1,9 @@
import { serviceActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
import attributeClassNames from '../util/attribute-class-names';
const { serviceActions } = hass;
const ATTRIBUTE_CLASSES = ['away_mode'];
export default new Polymer({
@ -44,13 +45,9 @@ export default new Polymer({
},
targetTemperatureSliderChanged(ev) {
const temp = parseInt(ev.target.value, 10);
if (isNaN(temp)) return;
serviceActions.callService('thermostat', 'set_temperature', {
entity_id: this.stateObj.entityId,
temperature: temp,
temperature: ev.target.value,
});
},

View File

@ -0,0 +1,65 @@
/* eslint-disable no-console */
const CACHE = '0.10';
const INDEX_CACHE_URL = '/';
const INDEX_ROUTES = ['/', '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent', '/devInfo', '/states'];
const CACHE_URLS = [
'/static/favicon-192x192.png',
];
if (__DEV__) {
console.log('Service Worker initialized.');
}
self.addEventListener('install', event => {
if (__DEV__) {
console.log('Service Worker installed.');
}
event.waitUntil(
caches.open(CACHE).then(cache => cache.addAll(CACHE_URLS.concat(INDEX_CACHE_URL)))
);
});
self.addEventListener('activate', event => {
if (__DEV__) {
console.log('Service Worker activated.');
// Force refresh service worker
event.waitUntil(global.clients.claim());
}
});
self.addEventListener('message', event => {
if (__DEV__) {
console.log('Message received', event.data);
}
});
self.addEventListener('fetch', event => {
const path = event.request.url.substr(event.request.url.indexOf('/', 8));
// TODO: do not cache requests to 3rd party hosts (or remove those calls)
if (CACHE_URLS.includes(path)) {
event.respondWith(
caches.open(CACHE).then(cache => cache.match(event.request))
);
}
if (!INDEX_ROUTES.includes(path)) {
return;
}
event.respondWith(
caches.open(CACHE).then(cache => {
return cache.match(INDEX_CACHE_URL).then(cachedResponse => {
const networkFetch = fetch(event.request).then(response => {
cache.put(INDEX_CACHE_URL, response.clone());
return response;
});
return cachedResponse || networkFetch;
});
})
);
});

View File

@ -6,3 +6,4 @@
<link rel="import" href="state-card-configurator.html">
<link rel="import" href="state-card-scene.html">
<link rel="import" href="state-card-media_player.html">
<link rel="import" href="state-card-rollershutter.html">

View File

@ -8,6 +8,7 @@ require('./state-card-thermostat');
require('./state-card-configurator');
require('./state-card-scene');
require('./state-card-media_player');
require('./state-card-rollershutter');
export default new Polymer({
is: 'state-card-content',

View File

@ -15,15 +15,18 @@
}
.main-text {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
text-transform: capitalize;
font-weight: 400;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
text-transform: capitalize;
font-weight: 400;
}
.secondary-text {
color: var(--secondary-text-color);
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
color: var(--secondary-text-color);
}
</style>
<template>

View File

@ -0,0 +1,31 @@
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="../components/state-info.html">
<dom-module id="state-card-rollershutter">
<style>
:host {
line-height: 1.5;
}
.state {
text-align: right;
white-space: nowrap;
width: 127px;
}
</style>
<template>
<div class='horizontal justified layout'>
<state-info state-obj="[[stateObj]]"></state-info>
<div class='state'>
<paper-icon-button icon="mdi:arrow-up" on-tap='onMoveUpTap'
disabled='[[computeIsFullyClosed(stateObj)]]'></paper-icon-button>
<paper-icon-button icon="mdi:stop" on-tap='onStopTap'></paper-icon-button>
<paper-icon-button icon="mdi:arrow-down" on-tap='onMoveDownTap'
disabled='[[computeIsFullyOpen(stateObj)]]'></paper-icon-button>
</div>
</div>
</template>
</dom-module>

View File

@ -0,0 +1,40 @@
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
require('../components/state-info');
const { serviceActions } = hass;
export default new Polymer({
is: 'state-card-rollershutter',
properties: {
stateObj: {
type: Object,
},
},
computeIsFullyOpen(stateObj) {
return stateObj.attributes.current_position === 100;
},
computeIsFullyClosed(stateObj) {
return stateObj.attributes.current_position === 0;
},
onMoveUpTap() {
serviceActions.callService('rollershutter', 'move_up',
{entity_id: this.stateObj.entityId});
},
onMoveDownTap() {
serviceActions.callService('rollershutter', 'move_down',
{entity_id: this.stateObj.entityId});
},
onStopTap() {
serviceActions.callService('rollershutter', 'stop',
{entity_id: this.stateObj.entityId});
},
});

View File

@ -1,8 +1,10 @@
import Polymer from '../polymer';
import { serviceActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
require('../components/state-info.js');
const { serviceActions } = hass;
export default new Polymer({
is: 'state-card-scene',

View File

@ -11,15 +11,22 @@
.state {
margin-left: 16px;
text-align: right;
overflow-x: hidden;
}
.target {
text-transform: capitalize;
font-weight: 400;
text-transform: capitalize;
font-weight: 400;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
.current {
color: var(--secondary-text-color);
color: var(--secondary-text-color);
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
</style>
<template>

View File

@ -1,9 +1,11 @@
import { moreInfoActions } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
import Polymer from '../polymer';
require('./state-card-content');
const { moreInfoActions } = hass;
export default new Polymer({
is: 'state-card',

View File

@ -1,5 +1,5 @@
import { reactor } from '../util/home-assistant-js-instance';
import hass from './home-assistant-js-instance';
import nuclearObserver from './nuclear-behavior';
export default nuclearObserver(reactor);
export default nuclearObserver(hass.reactor);

View File

@ -1,7 +1,9 @@
import {
import hass from './home-assistant-js-instance';
const {
reactor,
serviceGetters,
} from './home-assistant-js-instance';
} = hass;
// Return boolean if entity can be toggled.
export default function canToggle(entityId) {

View File

@ -3,7 +3,10 @@ import defaultIcon from './default-icon';
export default function domainIcon(domain, state) {
switch (domain) {
case 'alarm_control_panel':
return state && state === 'disarmed' ? 'mdi:lock-open' : 'mdi:lock';
return state && state === 'disarmed' ? 'mdi:bell-outline' : 'mdi:bell';
case 'binary_sensor':
return state && state === 'off' ? 'mdi:radiobox-blank' : 'mdi:checkbox-marked-circle';
case 'camera':
return 'mdi:video';
@ -26,6 +29,9 @@ export default function domainIcon(domain, state) {
case 'light':
return 'mdi:lightbulb';
case 'lock':
return state && state === 'unlocked' ? 'mdi:lock-open' : 'mdi:lock';
case 'media_player':
let icon = 'mdi:cast';
if (state && state !== 'off' && state !== 'idle') {
@ -40,14 +46,8 @@ export default function domainIcon(domain, state) {
case 'updater':
return 'mdi:cloud-upload';
case 'sun':
return 'mdi:white-balance-sunny';
case 'switch':
return 'mdi:flash';
case 'simple_alarm':
return 'mdi:bell';
case 'rollershutter':
return state && state === 'open' ? 'mdi:window-open' : 'mdi:window-closed';
case 'scene':
return 'mdi:google-pages';
@ -58,6 +58,15 @@ export default function domainIcon(domain, state) {
case 'sensor':
return 'mdi:eye';
case 'simple_alarm':
return 'mdi:bell';
case 'sun':
return 'mdi:white-balance-sunny';
case 'switch':
return 'mdi:flash';
case 'thermostat':
return 'mdi:nest-thermostat';

View File

@ -0,0 +1,7 @@
export default function removeInitMessage() {
// remove the HTML init message
const initMsg = document.getElementById('ha-init-skeleton');
if (initMsg) {
initMsg.parentElement.removeChild(initMsg);
}
}

View File

@ -1,7 +1,7 @@
import canToggle from './can-toggle';
const DOMAINS_WITH_CARD = [
'thermostat', 'configurator', 'scene', 'media_player'];
'thermostat', 'configurator', 'scene', 'media_player', 'rollershutter'];
export default function stateCardType(state) {
if (DOMAINS_WITH_CARD.indexOf(state.domain) !== -1) {

View File

@ -1,9 +1,8 @@
import defaultIcon from './default-icon';
import domainIcon from './domain-icon.js';
import {
util,
} from './home-assistant-js-instance';
import hass from './home-assistant-js-instance';
const { util: { temperatureUnits } } = hass;
export default function stateIcon(state) {
if (!state) {
@ -15,8 +14,8 @@ export default function stateIcon(state) {
const unit = state.attributes.unit_of_measurement;
if (unit && state.domain === 'sensor') {
if (unit === util.temperatureUnits.UNIT_TEMP_C ||
unit === util.temperatureUnits.UNIT_TEMP_F) {
if (unit === temperatureUnits.UNIT_TEMP_C ||
unit === temperatureUnits.UNIT_TEMP_F) {
return 'mdi:thermometer';
}
}

View File

@ -1,4 +1,6 @@
import { authActions, localStoragePreferences } from '../util/home-assistant-js-instance';
import hass from '../util/home-assistant-js-instance';
const { authActions, localStoragePreferences } = hass;
export default function(authToken, rememberAuth) {
authActions.validate(authToken, {

View File

@ -1,5 +1,4 @@
'use strict';
var path = require('path');
var webpack = require("webpack");
var definePlugin = new webpack.DefinePlugin({
@ -8,22 +7,28 @@ var definePlugin = new webpack.DefinePlugin({
});
module.exports = {
entry: "./src/home-assistant.js",
entry: {
_app_compiled: './src/home-assistant.js',
service_worker: './src/service-worker/index.js',
},
output: {
path: 'build',
filename: "_app_compiled.js"
path: 'build',
filename: '[name].js',
},
module: {
loaders: [
{
loader: "babel-loader",
loader: 'babel-loader',
test: /.js$/,
exclude: /node_modules\/(^home-assistant-js)/
}
]
include: [
path.resolve(__dirname, 'src'),
path.resolve(__dirname, 'node_modules/home-assistant-js/src'),
],
},
],
},
plugins: [
definePlugin,
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /no-other-locales-for-now/)
]
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /no-other-locales-for-now/),
],
};