mirror of
https://github.com/ckaczor/HomeStatus.git
synced 2026-01-13 17:22:55 -05:00
Initial commit to GitHub
This commit is contained in:
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
11
src/components/PressureTrend/PressureTrend.vue
Normal file
11
src/components/PressureTrend/PressureTrend.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<style lang="scss" src="./PressureTrend.vue.scss" scoped></style>
|
||||
|
||||
<script lang="ts" src="./PressureTrend.vue.ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="pressure-arrow-container">
|
||||
<div class="pressure-arrow">
|
||||
<v-icon :style="style()">fa-long-arrow-up fa-5x</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
11
src/components/PressureTrend/PressureTrend.vue.scss
Normal file
11
src/components/PressureTrend/PressureTrend.vue.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.pressure-arrow-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pressure-arrow {
|
||||
text-align: center;
|
||||
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
64
src/components/PressureTrend/PressureTrend.vue.ts
Normal file
64
src/components/PressureTrend/PressureTrend.vue.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
|
||||
import moment from 'moment';
|
||||
import regression from 'regression';
|
||||
|
||||
import { WeatherService, ValueType, HistoryEntry } from '@/services/WeatherService.ts';
|
||||
|
||||
@Component
|
||||
export default class PressureTrend extends Vue {
|
||||
pressureDifference: number | null = null;
|
||||
|
||||
async mounted() {
|
||||
this.update();
|
||||
|
||||
setInterval(this.update, 60000);
|
||||
}
|
||||
|
||||
async update() {
|
||||
const end: moment.Moment = moment();
|
||||
const start: moment.Moment = moment(end).subtract(3, 'hours');
|
||||
|
||||
const weatherData = await WeatherService.getDeviceHistory(ValueType.Pressure, start.toDate(), end.toDate());
|
||||
|
||||
if (!weatherData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const points: Array<Array<number>> = [];
|
||||
|
||||
weatherData[0].Value.forEach((historyEntry: HistoryEntry) => {
|
||||
if (historyEntry.Value >= 900 && historyEntry.Value <= 1050) {
|
||||
const point = [moment(historyEntry.ReadTime).unix(), historyEntry.Value];
|
||||
points.push(point);
|
||||
}
|
||||
});
|
||||
|
||||
const result = regression.linear(points, { precision: 10 });
|
||||
|
||||
const regressionPoints = result.points;
|
||||
|
||||
this.pressureDifference = regressionPoints[regressionPoints.length - 1][1] - regressionPoints[0][1];
|
||||
}
|
||||
|
||||
style(): string {
|
||||
let degrees: number = 0;
|
||||
|
||||
if (!this.pressureDifference) {
|
||||
degrees = 90;
|
||||
} else if (Math.abs(this.pressureDifference) <= 1.0) {
|
||||
degrees = 90;
|
||||
} else if (this.pressureDifference > 1.0 && this.pressureDifference <= 2.0) {
|
||||
degrees = 60;
|
||||
} else if (this.pressureDifference > 2.0) {
|
||||
degrees = 45;
|
||||
} else if (this.pressureDifference < -1.0 && this.pressureDifference >= -2.0) {
|
||||
degrees = 115;
|
||||
} else if (this.pressureDifference < -2.0) {
|
||||
degrees = 150;
|
||||
}
|
||||
|
||||
return `transform: rotate(${degrees}deg)`;
|
||||
}
|
||||
}
|
||||
15
src/config/Config.ts
Normal file
15
src/config/Config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { IConfig } from './IConfig';
|
||||
|
||||
class Config implements IConfig {
|
||||
weather = {
|
||||
host: null,
|
||||
port: 9090
|
||||
};
|
||||
|
||||
laundry = {
|
||||
host: null,
|
||||
port: 9091
|
||||
};
|
||||
}
|
||||
|
||||
export const config = new Config();
|
||||
9
src/config/IConfig.ts
Normal file
9
src/config/IConfig.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface IServerConfig {
|
||||
host: string | null;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
weather: IServerConfig;
|
||||
laundry: IServerConfig;
|
||||
}
|
||||
21
src/index.html
Normal file
21
src/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="static/favicons/favicon-32x32.png?v=oLdWoAx4Jd">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="static/favicons/favicon-16x16.png?v=oLdWoAx4Jd">
|
||||
<link rel="shortcut icon" href="static/favicons/favicon.ico?v=oLdWoAx4Jd">
|
||||
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
|
||||
|
||||
<title>Home Status</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
35
src/main.ts
Normal file
35
src/main.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import Vue from 'vue';
|
||||
import Vuetify from 'vuetify';
|
||||
|
||||
Vue.use(Vuetify);
|
||||
|
||||
import Highcharts from 'highcharts';
|
||||
import VueHighcharts from 'vue-highcharts';
|
||||
import highchartsMore from 'highcharts/highcharts-more';
|
||||
import highchartsExport from 'highcharts/modules/exporting';
|
||||
|
||||
highchartsMore(Highcharts);
|
||||
highchartsExport(Highcharts);
|
||||
|
||||
Vue.use(VueHighcharts, { Highcharts });
|
||||
|
||||
import router from './router';
|
||||
|
||||
import App from './views/App/App.vue';
|
||||
|
||||
import { WeatherService } from '@/services/WeatherService';
|
||||
import { LaundryService } from '@/services/LaundryService';
|
||||
|
||||
import { config } from '@/config/Config';
|
||||
|
||||
Promise.all([
|
||||
WeatherService.start(config.weather.host || localStorage['host'] || window.location.host, config.weather.port),
|
||||
LaundryService.start(config.laundry.host || localStorage['host'] || window.location.host, config.laundry.port)
|
||||
]).then(() => {
|
||||
new Vue({
|
||||
el: '#app',
|
||||
router,
|
||||
template: '<App/>',
|
||||
components: { App }
|
||||
});
|
||||
});
|
||||
35
src/router/index.ts
Normal file
35
src/router/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
import Dashboard from '@/views/Dashboard/Dashboard.vue';
|
||||
import Laundry from '@/views/Laundry/Laundry.vue';
|
||||
import Weather from '@/views/Weather/Weather.vue';
|
||||
import WeatherHistory from '@/views/WeatherHistory/WeatherHistory.vue';
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
path: '/laundry',
|
||||
name: 'Laundry',
|
||||
component: Laundry
|
||||
},
|
||||
{
|
||||
path: '/weather',
|
||||
name: 'Weather',
|
||||
component: Weather
|
||||
},
|
||||
{
|
||||
path: '/weather-history/:type',
|
||||
name: 'WeatherHistory',
|
||||
component: WeatherHistory
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
33
src/services/LaundryService.ts
Normal file
33
src/services/LaundryService.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import io from 'socket.io-client';
|
||||
|
||||
export class LaundryStatus {
|
||||
washer: boolean = false;
|
||||
dryer: boolean = false;
|
||||
}
|
||||
|
||||
export class LaundryService {
|
||||
static socket: SocketIOClient.Socket | null;
|
||||
static status: LaundryStatus = new LaundryStatus();
|
||||
|
||||
static start(server: string, port: number) {
|
||||
if (this.socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.socket = io(`http://${server}:${port}`);
|
||||
|
||||
this.socket.on('status', (statusString: string) => {
|
||||
const newStatus = JSON.parse(statusString);
|
||||
|
||||
if (newStatus.washer !== undefined) {
|
||||
this.status.washer = newStatus.washer;
|
||||
}
|
||||
|
||||
if (newStatus.dryer !== undefined) {
|
||||
this.status.dryer = newStatus.dryer;
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.emit('getStatus');
|
||||
}
|
||||
}
|
||||
100
src/services/WeatherService.ts
Normal file
100
src/services/WeatherService.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import Vue from 'vue';
|
||||
import { hubConnection, Connection, Proxy } from 'signalr-no-jquery';
|
||||
import moment from 'moment';
|
||||
|
||||
export class WeatherDeviceReading {
|
||||
Value: number;
|
||||
ReadTime: string;
|
||||
}
|
||||
|
||||
export class WindDirectionReading extends WeatherDeviceReading {
|
||||
WindDirectionString: string;
|
||||
}
|
||||
|
||||
export class RainReading extends WeatherDeviceReading {
|
||||
Inches: number;
|
||||
}
|
||||
|
||||
export class TemperatureReading extends WeatherDeviceReading {
|
||||
DegreesF: number;
|
||||
}
|
||||
|
||||
export class WeatherDeviceValue {
|
||||
ValueType: ValueType;
|
||||
Current: WeatherDeviceReading;
|
||||
}
|
||||
|
||||
export class WeatherDevice {
|
||||
Address: string;
|
||||
DisplayName: string;
|
||||
Errors: number;
|
||||
Id: number;
|
||||
Indoor: boolean;
|
||||
LastRead: string;
|
||||
Operations: number;
|
||||
RefreshFrequency: number;
|
||||
SupportedValues: Array<ValueType>;
|
||||
Type: number;
|
||||
Values: { [valueName: string]: WeatherDeviceValue };
|
||||
}
|
||||
|
||||
export enum ValueType {
|
||||
Temperature,
|
||||
Pressure,
|
||||
Humidity,
|
||||
WindSpeed,
|
||||
WindDirection,
|
||||
Rain
|
||||
}
|
||||
|
||||
export type HistoryEntry = {
|
||||
ValueType: ValueType;
|
||||
Value: number;
|
||||
ReadTime: string;
|
||||
};
|
||||
|
||||
export type HistoryResult = { Key: WeatherDevice, Value: Array<HistoryEntry> };
|
||||
|
||||
export class WeatherService {
|
||||
static deviceMap: { [deviceId: number]: WeatherDevice } = {};
|
||||
|
||||
private static connection: Connection;
|
||||
private static proxy: Proxy;
|
||||
|
||||
static async start(server: string, port: number) {
|
||||
this.connection = hubConnection(`http://${server}:${port}/signalr/`);
|
||||
|
||||
this.proxy = this.connection.createHubProxy('weatherHub');
|
||||
|
||||
await this.connection.start();
|
||||
|
||||
this.proxy.on('deviceRefreshed', (updatedDevice: WeatherDevice) => {
|
||||
Vue.set(this.deviceMap, updatedDevice.Id.toString(), updatedDevice);
|
||||
});
|
||||
|
||||
const devices = await this.proxy.invoke('getDevices');
|
||||
|
||||
devices.forEach((device: WeatherDevice) => {
|
||||
Vue.set(this.deviceMap, device.Id.toString(), device);
|
||||
});
|
||||
}
|
||||
|
||||
static async getDeviceHistory(valueType: ValueType, start: Date, end: Date): Promise<any[] | null> {
|
||||
const startString = moment(start).toISOString();
|
||||
const endString = moment(end).toISOString();
|
||||
|
||||
if (valueType === ValueType.WindDirection) {
|
||||
const data = await this.proxy.invoke('getWindDirectionHistory', startString, endString);
|
||||
|
||||
return data;
|
||||
} else if (valueType === ValueType.WindSpeed) {
|
||||
const data = await this.proxy.invoke('getWindSpeedHistory', 5, startString, endString);
|
||||
|
||||
return data;
|
||||
} else {
|
||||
const data = await this.proxy.invoke('getGenericHistory', valueType, startString, endString);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/views/App/App.vue
Normal file
51
src/views/App/App.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<style lang="scss" src="./App.vue.scss"></style>
|
||||
|
||||
<script lang="ts" src="./App.vue.ts"></script>
|
||||
|
||||
<template>
|
||||
<v-app id="inspire" light>
|
||||
<v-navigation-drawer clipped fixed v-model="drawer" app width="250">
|
||||
<v-list dense>
|
||||
<span v-for="item in items" v-bind:key="item.title">
|
||||
<v-list-group v-if="item.items" :value="item.active" v-model="item.expanded">
|
||||
<v-list-tile slot="item" :to="item.to">
|
||||
<v-list-tile-action>
|
||||
<v-icon>{{ item.action }}</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
<v-list-tile-action>
|
||||
<v-icon>keyboard_arrow_down</v-icon>
|
||||
</v-list-tile-action>
|
||||
</v-list-tile>
|
||||
<v-list-tile v-for="subItem in item.items" v-bind:key="subItem.title" :to="subItem.to">
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ subItem.title }}</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list-group>
|
||||
<v-list-tile v-else slot="item" :to="item.to" exact>
|
||||
<v-list-tile-action>
|
||||
<v-icon>{{ item.action }}</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</span>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-toolbar app fixed clipped-left color="indigo" dark dense flat>
|
||||
<v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
|
||||
<v-toolbar-title>Home Status</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-content>
|
||||
<v-container fluid fill-height>
|
||||
<v-layout>
|
||||
<router-view></router-view>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-content>
|
||||
</v-app>
|
||||
</template>
|
||||
43
src/views/App/App.vue.scss
Normal file
43
src/views/App/App.vue.scss
Normal file
@@ -0,0 +1,43 @@
|
||||
$fa-font-path: "~font-awesome/fonts";
|
||||
@import "~font-awesome/scss/font-awesome";
|
||||
|
||||
@import '@/../vuetify/dist/vuetify.min.css';
|
||||
|
||||
html {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: "Avenir", Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.view-container {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.view-loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 998;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.view-loading-progress {
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 35%;
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
67
src/views/App/App.vue.ts
Normal file
67
src/views/App/App.vue.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
import { ValueType, WeatherService } from '@/services/WeatherService';
|
||||
import { LaundryService } from '@/services/LaundryService';
|
||||
|
||||
@Component
|
||||
export default class App extends Vue {
|
||||
drawer: boolean | null = null;
|
||||
|
||||
items: any = [
|
||||
{
|
||||
action: 'home',
|
||||
title: 'Dashboard',
|
||||
to: '/'
|
||||
},
|
||||
{
|
||||
action: 'local_laundry_service',
|
||||
title: 'Laundry',
|
||||
to: '/laundry'
|
||||
},
|
||||
{
|
||||
action: 'cloud',
|
||||
title: 'Weather',
|
||||
to: '/weather'
|
||||
},
|
||||
{
|
||||
action: 'multiline_chart',
|
||||
title: 'Weather Charts',
|
||||
expanded: false,
|
||||
route: 'WeatherHistory',
|
||||
items: [
|
||||
{
|
||||
title: 'Temperature',
|
||||
to: '/weather-history/' + ValueType.Temperature
|
||||
},
|
||||
{
|
||||
title: 'Pressure',
|
||||
to: '/weather-history/' + ValueType.Pressure
|
||||
},
|
||||
{
|
||||
title: 'Humidity',
|
||||
to: '/weather-history/' + ValueType.Humidity
|
||||
},
|
||||
{
|
||||
title: 'Wind direction',
|
||||
to: '/weather-history/' + ValueType.WindDirection
|
||||
},
|
||||
{
|
||||
title: 'Wind speed',
|
||||
to: '/weather-history/' + ValueType.WindSpeed
|
||||
},
|
||||
{
|
||||
title: 'Rain',
|
||||
to: '/weather-history/' + ValueType.Rain
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
async mounted() {
|
||||
this.items.forEach((item: any) => {
|
||||
if (item.route) {
|
||||
item.expanded = this.$route.name === item.route;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
69
src/views/Dashboard/Dashboard.vue
Normal file
69
src/views/Dashboard/Dashboard.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<style lang="scss" src="./Dashboard.vue.scss" scoped></style>
|
||||
|
||||
<script lang="ts" src="./Dashboard.vue.ts"></script>
|
||||
|
||||
<style lang="scss" src="./Grid.scss"></style>
|
||||
|
||||
<template>
|
||||
<div id="dashboard-container" v-if="ready">
|
||||
<v-toolbar height="42" flat>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn small outline color="grey darken-1" @click="startEdit()">
|
||||
<v-icon left class="button-icon">edit</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn small outline color="grey darken-1" @click="toggleLocked()">
|
||||
<v-icon left class="button-icon">lock</v-icon>
|
||||
{{ locked ? 'Unlock' : 'Lock' }}
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-dialog class="dashboard-panels-dialog" v-model="editing" persistent width="50%" scrollable>
|
||||
<v-card>
|
||||
<v-card-title class="indigo dashboard-panels-header">
|
||||
<span class="title white--text">
|
||||
Dashboard Panels
|
||||
</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon="icon" v-on:click="editing = false" class="white--text">
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="dashboard-panels-text">
|
||||
<div class="dashboard-panels-item" v-for="panel in allPanels" :key="panel.componentName">
|
||||
<div class="dashboard-panels-item-name">
|
||||
{{ panel.name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ panel.description }}
|
||||
</div>
|
||||
<v-btn class="dashboard-panels-item-button" color="primary" small white--text @click="addPanel(panel)" v-if="!isPanelAdded(panel)">
|
||||
Add
|
||||
</v-btn>
|
||||
<v-btn class="dashboard-panels-item-button" color="primary" small white--text @click="removePanel(panel)" v-if="isPanelAdded(panel)">
|
||||
Remove
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<grid-layout :layout="panels" :col-num="100" :row-height="10" :is-draggable="!locked" :is-resizable="!locked" :vertical-compact="false" :margin="[10, 10]" :use-css-transforms="true" @layout-updated="savePanels">
|
||||
<grid-item v-for="panel in panels" :key="panel.i" :x="panel.x" :y="panel.y" :w="panel.w" :h="panel.h" :i="panel.i">
|
||||
<v-card flat class="dashboard-panel">
|
||||
<v-card-title class="indigo dashboard-panel-header">
|
||||
<span class="title white--text">
|
||||
{{ panel.name }}
|
||||
</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-show="!locked" small icon title="Remove" @click="removePanel(panel)" dark>
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="dashboard-panel-text">
|
||||
<component :is="panel.componentName"></component>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
</div>
|
||||
</template>
|
||||
79
src/views/Dashboard/Dashboard.vue.scss
Normal file
79
src/views/Dashboard/Dashboard.vue.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
.vue-grid-item {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.vue-grid-item:not(.vue-grid-placeholder) {
|
||||
border: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
#dashboard-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.dashboard-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dashboard-panel-header {
|
||||
height: 26px;
|
||||
min-height: 26px;
|
||||
padding-right: 0;
|
||||
|
||||
& .title {
|
||||
font-size: 10pt !important;
|
||||
}
|
||||
|
||||
& button .icon {
|
||||
font-size: 12pt;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-panel-text {
|
||||
padding: 6px 14px;
|
||||
height: calc(100% - 34px);
|
||||
}
|
||||
|
||||
.dashboard-panels-header {
|
||||
height: 48px;
|
||||
min-height: 48px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.dashboard-panels-text {
|
||||
padding: 0;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.dashboard-panels-item {
|
||||
padding: 10px 15px;
|
||||
position: relative;
|
||||
|
||||
&:nth-child(2) {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-panels-item-name {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dashboard-panels-item-button {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 5px;
|
||||
}
|
||||
102
src/views/Dashboard/Dashboard.vue.ts
Normal file
102
src/views/Dashboard/Dashboard.vue.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
|
||||
import VueGridLayout from 'vue-grid-layout';
|
||||
|
||||
import Laundry from '@/views/Laundry/Laundry.vue';
|
||||
import Weather from '@/views/Weather/Weather.vue';
|
||||
import PressureTrend from '@/components/PressureTrend/PressureTrend.vue';
|
||||
|
||||
Vue.component('GridLayout', VueGridLayout.GridLayout);
|
||||
Vue.component('GridItem', VueGridLayout.GridItem);
|
||||
|
||||
Vue.component('Laundry', Laundry);
|
||||
Vue.component('Weather', Weather);
|
||||
Vue.component('PressureTrend', PressureTrend);
|
||||
|
||||
class DashboardPanel {
|
||||
name: string;
|
||||
componentName: string;
|
||||
description: string;
|
||||
|
||||
defaultSize: { height: number, width: number };
|
||||
}
|
||||
|
||||
class DashboardPanelLayout {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
i: string;
|
||||
|
||||
name: string;
|
||||
componentName: string;
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class Dashboard extends Vue {
|
||||
ready: boolean = false;
|
||||
|
||||
locked: boolean = true;
|
||||
editing: boolean = false;
|
||||
|
||||
panels: Array<DashboardPanelLayout> = [];
|
||||
|
||||
allPanels: Array<DashboardPanel> = [
|
||||
{ name: 'Weather', componentName: 'Weather', description: 'Text summary of current weather conditions', defaultSize: { height: 10, width: 30 } },
|
||||
{ name: 'Laundry', componentName: 'Laundry', description: 'Current washer and dryer status', defaultSize: { height: 6, width: 15 } },
|
||||
{ name: 'Pressure Trend', componentName: 'PressureTrend', description: 'An arrow showing the barometric pressure trend for the last three hours.', defaultSize: { height: 8, width: 20 } }
|
||||
];
|
||||
|
||||
mounted() {
|
||||
const savedPanels = localStorage.getItem('panels');
|
||||
|
||||
if (savedPanels) {
|
||||
this.panels = JSON.parse(savedPanels);
|
||||
} else {
|
||||
this.allPanels.forEach((panel) => this.addPanel(panel));
|
||||
}
|
||||
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
toggleLocked() {
|
||||
this.locked = !this.locked;
|
||||
}
|
||||
|
||||
startEdit() {
|
||||
this.editing = true;
|
||||
}
|
||||
|
||||
savePanels() {
|
||||
const savedPanels = JSON.stringify(this.panels);
|
||||
|
||||
localStorage.setItem('panels', savedPanels);
|
||||
}
|
||||
|
||||
isPanelAdded(panel: DashboardPanel) {
|
||||
return this.panels.find((currentPanel) => currentPanel.componentName === panel.componentName);
|
||||
}
|
||||
|
||||
addPanel(panel: DashboardPanel) {
|
||||
this.panels.push({
|
||||
x: 0,
|
||||
y: 0,
|
||||
h: panel.defaultSize.height,
|
||||
w: panel.defaultSize.width,
|
||||
i: this.panels.length.toString(),
|
||||
name: panel.name,
|
||||
componentName: panel.componentName,
|
||||
});
|
||||
|
||||
this.savePanels();
|
||||
}
|
||||
|
||||
removePanel(panel: DashboardPanel) {
|
||||
const index = this.panels.findIndex((currentPanel) => currentPanel.componentName === panel.componentName);
|
||||
|
||||
this.panels.splice(index, 1);
|
||||
|
||||
this.savePanels();
|
||||
}
|
||||
}
|
||||
3
src/views/Dashboard/Grid.scss
Normal file
3
src/views/Dashboard/Grid.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.vue-grid-item.vue-grid-placeholder {
|
||||
background-color: #3f51b5;
|
||||
}
|
||||
26
src/views/Laundry/Laundry.vue
Normal file
26
src/views/Laundry/Laundry.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<style lang="scss" src="./Laundry.vue.scss" scoped></style>
|
||||
|
||||
<script lang="ts" src="./Laundry.vue.ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="device-name">
|
||||
Washer
|
||||
</td>
|
||||
<td :class="laundryStatus.washer.toString()">
|
||||
{{ laundryStatus.washer ? 'On' : 'Off' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="device-name">
|
||||
Dryer
|
||||
</td>
|
||||
<td :class="laundryStatus.dryer.toString()">
|
||||
{{ laundryStatus.dryer ? 'On' : 'Off' }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
13
src/views/Laundry/Laundry.vue.scss
Normal file
13
src/views/Laundry/Laundry.vue.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
.device-name {
|
||||
font-weight: bold;
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.true {
|
||||
color: darkgoldenrod;
|
||||
}
|
||||
|
||||
.false {
|
||||
color: darkgreen;
|
||||
}
|
||||
9
src/views/Laundry/Laundry.vue.ts
Normal file
9
src/views/Laundry/Laundry.vue.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
|
||||
import { LaundryService, LaundryStatus } from '@/services/LaundryService.ts';
|
||||
|
||||
@Component
|
||||
export default class Laundry extends Vue {
|
||||
laundryStatus: LaundryStatus = LaundryService.status;
|
||||
}
|
||||
18
src/views/Weather/Weather.vue
Normal file
18
src/views/Weather/Weather.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<style lang="scss" src="./Weather.vue.scss" scoped></style>
|
||||
|
||||
<script lang="ts" src="./Weather.vue.ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<table>
|
||||
<tr v-for="device in devices" v-bind:key="device.Id">
|
||||
<td class="device-name">
|
||||
{{ device.DisplayName }}
|
||||
</td>
|
||||
<td>
|
||||
{{ formatDevice(device) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
5
src/views/Weather/Weather.vue.scss
Normal file
5
src/views/Weather/Weather.vue.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
.device-name {
|
||||
font-weight: bold;
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
60
src/views/Weather/Weather.vue.ts
Normal file
60
src/views/Weather/Weather.vue.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop } from 'vue-property-decorator';
|
||||
|
||||
import { WeatherService, WeatherDevice, ValueType, TemperatureReading, RainReading, WindDirectionReading, WeatherDeviceValue } from '@/services/WeatherService.ts';
|
||||
|
||||
@Component
|
||||
export default class Weather extends Vue {
|
||||
private deviceMap = WeatherService.deviceMap;
|
||||
|
||||
formatDevice(device: WeatherDevice): string {
|
||||
let valueDisplay: string = '';
|
||||
|
||||
for (const value of Object.values(device.Values)) {
|
||||
|
||||
switch (value.ValueType) {
|
||||
case ValueType.Temperature:
|
||||
const tempReading = value.Current as TemperatureReading;
|
||||
|
||||
valueDisplay += ' ' + tempReading.DegreesF.toFixed(2) + '°F';
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.Humidity:
|
||||
valueDisplay += ' ' + value.Current.Value.toFixed(2) + '%';
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.Pressure:
|
||||
valueDisplay += ' ' + value.Current.Value.toFixed(2) + ' hPa';
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.Rain:
|
||||
const rainReading = value.Current as RainReading;
|
||||
|
||||
valueDisplay += ' ' + rainReading.Inches.toFixed(2) + '"';
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.WindSpeed:
|
||||
valueDisplay += ' ' + value.Current.Value.toFixed(2) + ' MPH';
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.WindDirection:
|
||||
const windReading = value.Current as WindDirectionReading;
|
||||
|
||||
valueDisplay += ' ' + windReading.WindDirectionString;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return valueDisplay;
|
||||
}
|
||||
|
||||
get devices(): Array<WeatherDevice> {
|
||||
return Object.values(this.deviceMap).sort((a, b) => a.Type - b.Type);
|
||||
}
|
||||
}
|
||||
35
src/views/WeatherHistory/Chart.scss
Normal file
35
src/views/WeatherHistory/Chart.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
#chart-subtitle {
|
||||
cursor: pointer;
|
||||
color: #1976d2;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-day-arrow {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:not(.disabled) {
|
||||
cursor: pointer;
|
||||
color: #1976d2;
|
||||
}
|
||||
}
|
||||
|
||||
#chart-day-previous {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#chart-day-next {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.chart-settings {
|
||||
margin-left: 250px;
|
||||
}
|
||||
48
src/views/WeatherHistory/WeatherHistory.vue
Normal file
48
src/views/WeatherHistory/WeatherHistory.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<style lang="scss" src="./WeatherHistory.vue.scss" scoped></style>
|
||||
|
||||
<script lang="ts" src="./WeatherHistory.vue.ts"></script>
|
||||
|
||||
<style lang="scss" src="./Chart.scss"></style>
|
||||
|
||||
<template>
|
||||
<div class="view-container">
|
||||
<div class="view-loading-overlay" v-if="loading">
|
||||
<v-progress-circular indeterminate class="view-loading-progress indigo--text" size="64" />
|
||||
</div>
|
||||
|
||||
<div v-if="ready" id="chart-container">
|
||||
<v-toolbar height="42" flat>
|
||||
<v-menu offset-y>
|
||||
<v-btn small outline slot="activator" color="grey darken-1">
|
||||
{{ timeSpanItems[selectedTimeSpan] }}
|
||||
</v-btn>
|
||||
<v-list dense>
|
||||
<v-list-tile v-for="(text, value) in timeSpanItems" :key="value" @click="selectedTimeSpan = Number(value)">
|
||||
<v-list-tile-title>{{ text }}</v-list-tile-title>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-btn v-show="selectedTimeSpan === timeSpans.Day" small outline color="grey darken-1" @click="handleDateArrowClick(-1)">
|
||||
<v-icon>skip_previous</v-icon>
|
||||
</v-btn>
|
||||
<v-menu v-show="selectedTimeSpan === timeSpans.Day" lazy :close-on-content-click="false" v-model="showDateMenu" offset-y full-width>
|
||||
<v-btn id="date-button" small outline slot="activator" color="grey darken-1">
|
||||
{{ getSelectedDateDisplayString() }}
|
||||
</v-btn>
|
||||
|
||||
<v-date-picker v-model="selectedDateIsoString" no-title autosave></v-date-picker>
|
||||
</v-menu>
|
||||
<v-btn v-show="selectedTimeSpan === timeSpans.Day && !isSelectedDateToday()" small outline color="grey darken-1" @click="handleDateArrowClick(1)">
|
||||
<v-icon>skip_next</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn v-show="selectedTimeSpan === timeSpans.Day && !isSelectedDateToday()" small outline color="grey darken-1" @click="resetToToday">
|
||||
Today
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<highcharts id="chart" :options="chartConfig" ref="highcharts"></highcharts>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
20
src/views/WeatherHistory/WeatherHistory.vue.scss
Normal file
20
src/views/WeatherHistory/WeatherHistory.vue.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
#chart-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#chart {
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#date-button {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
360
src/views/WeatherHistory/WeatherHistory.vue.ts
Normal file
360
src/views/WeatherHistory/WeatherHistory.vue.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop, Watch } from 'vue-property-decorator';
|
||||
import moment from 'moment';
|
||||
|
||||
import { WeatherService, ValueType } from '@/services/WeatherService.ts';
|
||||
import * as Highcharts from 'highcharts';
|
||||
import { AxisOptions } from 'highcharts';
|
||||
|
||||
enum TimeSpan {
|
||||
Last24Hours,
|
||||
Day,
|
||||
Custom
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class Weather extends Vue {
|
||||
loading: boolean = true;
|
||||
ready: boolean = false;
|
||||
|
||||
selectedValueType: ValueType | null = null;
|
||||
selectedTimeSpan: TimeSpan = TimeSpan.Last24Hours;
|
||||
selectedDate: moment.Moment = moment().startOf('day');
|
||||
|
||||
timeSpans: typeof TimeSpan = TimeSpan;
|
||||
timeSpanItems: { [value: number]: string } = {};
|
||||
|
||||
chartConfig: Highcharts.Options | null = null;
|
||||
|
||||
showDateMenu: boolean = false;
|
||||
|
||||
async mounted() {
|
||||
Highcharts.setOptions({
|
||||
global: {
|
||||
useUTC: false
|
||||
}
|
||||
});
|
||||
|
||||
this.timeSpanItems[TimeSpan.Last24Hours] = 'Last 24 hours';
|
||||
this.timeSpanItems[TimeSpan.Day] = 'Day';
|
||||
|
||||
this.selectedValueType = Number(this.$route.params['type']);
|
||||
}
|
||||
|
||||
prepareData(deviceList: any, displayName: string = '', valueName: string, minValue?: number, additive?: boolean) {
|
||||
const chartData: any[] = [];
|
||||
|
||||
deviceList.forEach((device: any) => {
|
||||
let deviceName;
|
||||
|
||||
if (typeof device.Key === 'string') {
|
||||
deviceName = device.Key;
|
||||
} else {
|
||||
deviceName = displayName === undefined ? device.Key : device.Key[displayName];
|
||||
}
|
||||
|
||||
const deviceData = {
|
||||
name: deviceName,
|
||||
data: [] as any
|
||||
};
|
||||
|
||||
let previousValue: number | null = null;
|
||||
|
||||
device.Value.forEach((value: any) => {
|
||||
let currentValue: number | null = value[valueName];
|
||||
const readTime = moment(value.ReadTime);
|
||||
|
||||
if (minValue && currentValue) {
|
||||
if (currentValue < minValue) {
|
||||
currentValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentValue != null) {
|
||||
if (additive && previousValue !== null) {
|
||||
currentValue += previousValue;
|
||||
}
|
||||
}
|
||||
|
||||
deviceData.data.push([readTime.valueOf(), currentValue] as any);
|
||||
|
||||
previousValue = currentValue;
|
||||
});
|
||||
|
||||
chartData.push(deviceData);
|
||||
});
|
||||
|
||||
return chartData;
|
||||
}
|
||||
|
||||
prepareDataByValueType(valueType: ValueType, deviceData: any): any {
|
||||
let chartData: any;
|
||||
|
||||
switch (valueType) {
|
||||
case ValueType.Temperature:
|
||||
chartData = this.prepareData(deviceData, 'DisplayName', 'DegreesF', -40);
|
||||
|
||||
return { chartData: chartData, categoryData: undefined };
|
||||
|
||||
case ValueType.Pressure:
|
||||
chartData = this.prepareData(deviceData, 'DisplayName', 'Value', 850);
|
||||
|
||||
return { chartData: chartData, categoryData: undefined };
|
||||
|
||||
case ValueType.Humidity:
|
||||
chartData = this.prepareData(deviceData, 'DisplayName', 'Value', 0);
|
||||
|
||||
return { chartData: chartData, categoryData: undefined };
|
||||
|
||||
case ValueType.WindDirection:
|
||||
const categoryData: any[] = [];
|
||||
|
||||
chartData = [];
|
||||
|
||||
deviceData.forEach((device: any) => {
|
||||
categoryData.push(device.Key);
|
||||
chartData.push(device.Value);
|
||||
});
|
||||
|
||||
return { chartData: chartData, categoryData: categoryData };
|
||||
|
||||
case ValueType.WindSpeed:
|
||||
chartData = this.prepareData(deviceData, undefined, 'Value');
|
||||
|
||||
return { chartData: chartData, categoryData: undefined };
|
||||
|
||||
case ValueType.Rain:
|
||||
chartData = this.prepareData(deviceData, 'DisplayName', 'Inches', undefined, true);
|
||||
|
||||
return { chartData: chartData, categoryData: undefined };
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
loadChart(chartData: any, categoryData: any) {
|
||||
switch (this.selectedValueType) {
|
||||
case ValueType.Temperature:
|
||||
this.chartConfig = this.createChartConfig(chartData, 'Temperature', 'Degrees F', '°F');
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.Pressure:
|
||||
this.chartConfig = this.createChartConfig(chartData, 'Pressure', 'hPa', ' hPa');
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.Humidity:
|
||||
this.chartConfig = this.createChartConfig(chartData, 'Humidity', '%', '%');
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.WindDirection:
|
||||
this.chartConfig = {
|
||||
chart: {
|
||||
polar: true,
|
||||
type: 'column'
|
||||
},
|
||||
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
categories: categoryData,
|
||||
tickmarkPlacement: 'on'
|
||||
},
|
||||
|
||||
yAxis: {
|
||||
labels: {
|
||||
enabled: false
|
||||
}
|
||||
},
|
||||
|
||||
plotOptions: {
|
||||
series: {
|
||||
shadow: false,
|
||||
pointPlacement: 'on',
|
||||
animation: false
|
||||
},
|
||||
column: {
|
||||
groupPadding: 0
|
||||
}
|
||||
},
|
||||
|
||||
title: {
|
||||
text: 'Wind Direction'
|
||||
},
|
||||
|
||||
series: [{
|
||||
type: 'column',
|
||||
name: 'Samples',
|
||||
data: chartData
|
||||
}]
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.WindSpeed:
|
||||
this.chartConfig = this.createChartConfig(chartData, 'Wind Speed', 'MPH', ' MPH');
|
||||
|
||||
if (this.chartConfig.yAxis) {
|
||||
(this.chartConfig.yAxis as AxisOptions).min = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ValueType.Rain:
|
||||
this.chartConfig = this.createChartConfig(chartData, 'Rain', '', '"');
|
||||
|
||||
if (this.chartConfig.yAxis) {
|
||||
(this.chartConfig.yAxis as AxisOptions).min = 0;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
this.chartConfig = this.createChartConfig(null, '', '', '');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
createChartConfig(chartData: any, title: string, yAxisTitle: string, tooltipSuffix: string): Highcharts.Options {
|
||||
const chartConfig = {
|
||||
chart: {
|
||||
type: 'line',
|
||||
zoomType: 'x'
|
||||
},
|
||||
tooltip: {
|
||||
xDateFormat: '%A %B %e: %I:%M:%S %p',
|
||||
valueDecimals: 3,
|
||||
valueSuffix: tooltipSuffix
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime',
|
||||
dateTimeLabelFormats: {
|
||||
minute: '%I:%M %p',
|
||||
hour: '%I:%M %p',
|
||||
second: '%I:%M:%S %p',
|
||||
day: '%I:%M %p'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
labels: {
|
||||
formatter(): string {
|
||||
return (this as any).value + tooltipSuffix;
|
||||
}
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
marker: {
|
||||
enabled: false
|
||||
},
|
||||
animation: false
|
||||
}
|
||||
},
|
||||
title: {
|
||||
text: title,
|
||||
y: 18
|
||||
},
|
||||
series: chartData
|
||||
};
|
||||
|
||||
return chartConfig;
|
||||
}
|
||||
|
||||
@Watch('$route')
|
||||
onRouteChange() {
|
||||
this.selectedValueType = Number(this.$route.params['type']);
|
||||
}
|
||||
|
||||
@Watch('selectedValueType')
|
||||
@Watch('selectedTimeSpan')
|
||||
@Watch('selectedDate')
|
||||
async refreshChart() {
|
||||
if (this.selectedValueType === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
let start: Date;
|
||||
let end: Date;
|
||||
|
||||
if (this.selectedTimeSpan === TimeSpan.Custom) {
|
||||
// start = moment('2014-01-01 00:00:00 -05:00').toDate();
|
||||
// end = moment('2015-01-01 00:00:00 -05:00').toDate();
|
||||
|
||||
// weatherService.getDailySummary($scope.selectedValueType.id, $scope.selectedDevice.id, start, end).done(data => {
|
||||
// var preparedData = this.prepareDataByValueType($scope.selectedValueType.id, data);
|
||||
// this.loadChart($scope, preparedData.chartData, preparedData.categoryData);
|
||||
|
||||
// $scope.chartConfig.loading = false;
|
||||
// $scope.$apply();
|
||||
// });
|
||||
} else {
|
||||
switch (this.selectedTimeSpan) {
|
||||
case TimeSpan.Last24Hours: {
|
||||
start = moment().subtract(24, 'h').toDate();
|
||||
end = moment().toDate();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case TimeSpan.Day: {
|
||||
start = moment(this.selectedDate).startOf('d').toDate();
|
||||
end = moment(this.selectedDate).endOf('d').toDate();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const deviceData = await WeatherService.getDeviceHistory(this.selectedValueType, start, end);
|
||||
|
||||
const preparedData = this.prepareDataByValueType(this.selectedValueType, deviceData);
|
||||
this.loadChart(preparedData.chartData, preparedData.categoryData);
|
||||
|
||||
this.loading = false;
|
||||
|
||||
this.ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
handleDateArrowClick(value: number) {
|
||||
this.selectedDate.add(value, 'day');
|
||||
|
||||
this.refreshChart();
|
||||
}
|
||||
|
||||
isSelectedDateToday(): boolean {
|
||||
const isToday = this.selectedDate.startOf('day').isSame(moment().startOf('day'));
|
||||
|
||||
return isToday;
|
||||
}
|
||||
|
||||
get selectedDateIsoString(): string {
|
||||
return this.selectedDate.format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
set selectedDateIsoString(value: string) {
|
||||
this.selectedDate = moment(value);
|
||||
}
|
||||
|
||||
getSelectedDateDisplayString(): string {
|
||||
return this.selectedDate.format('LL');
|
||||
}
|
||||
|
||||
resetToToday() {
|
||||
this.selectedDate = moment().startOf('day');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user