From a949af38a33cd08577ab91d6c5eb418520daefc8 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Fri, 30 Oct 2015 20:28:41 +0100 Subject: web: o Adding 'time ago' for each value. o Adding 'reload' button for a property. --- bower.json | 3 +- src/DillerDao.js | 4 +- web/static/app/app.css | 152 ++++++++++++++++++++++++++++++++- web/static/app/app.js | 56 +++++++++++- web/static/app/templates/property.html | 12 ++- web/templates/index.jade | 1 + 6 files changed, 218 insertions(+), 10 deletions(-) diff --git a/bower.json b/bower.json index 0138bf1..d6219e6 100644 --- a/bower.json +++ b/bower.json @@ -16,6 +16,7 @@ "angular": "~1.4.7", "angular-route": "~1.4.7", "bootstrap": "4.0.0-alpha", - "lodash": "~3.10.1" + "lodash": "~3.10.1", + "moment": "~2.10.6" } } diff --git a/src/DillerDao.js b/src/DillerDao.js index 98b6e6e..3a6601f 100644 --- a/src/DillerDao.js +++ b/src/DillerDao.js @@ -71,7 +71,7 @@ function DillerDao(tx) { function updateHourAggregatesForProperty(propertyId, timestamp) { return tx.none('DELETE FROM value_by_hour WHERE property=$1 AND timestamp=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ)', [propertyId, timestamp]) .then(function() { - return tx.one('INSERT INTO value_by_hour(property, timestamp, count, max, min, avg) ' + + return tx.oneOrNone('INSERT INTO value_by_hour(property, timestamp, count, max, min, avg) ' + 'SELECT property, DATE_TRUNC(\'hour\', timestamp) AS timestamp, COUNT(value_numeric) AS count, MAX(value_numeric) AS max, MIN(value_numeric) AS min, AVG(value_numeric) AS avg ' + 'FROM value WHERE property=$1 AND DATE_TRUNC(\'hour\', timestamp)=DATE_TRUNC(\'hour\', $2::TIMESTAMPTZ) AND value_numeric IS NOT NULL ' + 'GROUP BY property, DATE_TRUNC(\'hour\', timestamp) ' + @@ -82,7 +82,7 @@ function DillerDao(tx) { function updateMinuteAggregatesForProperty(propertyId, timestamp) { return tx.none('DELETE FROM value_by_minute WHERE property=$1 AND timestamp=DATE_TRUNC(\'minute\', $2::TIMESTAMPTZ)', [propertyId, timestamp]) .then(function() { - return tx.one('INSERT INTO value_by_minute(property, timestamp, count, max, min, avg) ' + + return tx.oneOrNone('INSERT INTO value_by_minute(property, timestamp, count, max, min, avg) ' + 'SELECT property, DATE_TRUNC(\'minute\', timestamp) AS timestamp, COUNT(value_numeric) AS count, MAX(value_numeric) AS max, MIN(value_numeric) AS min, AVG(value_numeric) AS avg ' + 'FROM value ' + 'WHERE property=$1 AND DATE_TRUNC(\'minute\', timestamp)=DATE_TRUNC(\'minute\', $2::TIMESTAMPTZ) AND value_numeric IS NOT NULL ' + diff --git a/web/static/app/app.css b/web/static/app/app.css index 090ac5e..f21c0dc 100644 --- a/web/static/app/app.css +++ b/web/static/app/app.css @@ -1,3 +1,153 @@ nav.navbar { - margin-bottom: 2rem; + margin-bottom: 2rem; } + +/* + * http://martinwolf.org/2015/01/08/pure-css-savingloading-dots-animation/ + */ +.dl-dots span { + animation-name: dl-dots; + animation-duration: 1.4s; + animation-iteration-count: infinite; + animation-fill-mode: both; +} + +.dl-dots span:nth-child(2) { + animation-delay: .2s; +} + +.dl-dots span:nth-child(3) { + animation-delay: .4s; +} + +@-webkit-keyframes dl-dots { + 0% { + opacity: .2; + } + 20% { + opacity: 1; + } + 100% { + opacity: .2; + } +} + +@-moz-keyframes dl-dots { + 0% { + opacity: .2; + } + 20% { + opacity: 1; + } + 100% { + opacity: .2; + } +} + +@-ms-keyframes dl-dots { + 0% { + opacity: .2; + } + 20% { + opacity: 1; + } + 100% { + opacity: .2; + } +} + +@keyframes dl-dots { + 0% { + opacity: .2; + } + 20% { + opacity: 1; + } + 100% { + opacity: .2; + } +} + +/* + * https://gist.github.com/jankorbel/2336513/cb2f68b59521438ac812ad72c1c93d8f656e49ad + */ +/* +.dl-dots span { + background: transparent; + border-radius: 50%; + box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.3); + display: inline-block; + height: 0.6em; + width: 0.6em; + + -webkit-animation: dl-dots 0.8s linear infinite; + -moz-animation: dl-dots 0.8s linear infinite; + -ms-animation: dl-dots 0.8s linear infinite; + animation: dl-dots 0.8s linear infinite; +} + +.dl-dots span:nth-child(1) { + -webkit-animation-delay: 0.4s; + -moz-animation-delay: 0.4s; + -ms-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +.dl-dots span:nth-child(2) { + -webkit-animation-delay: 0.2s; + -moz-animation-delay: 0.2s; + -ms-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +!* + Define the animation for every efing vendor prefix +*! +@-webkit-keyframes dl-dots { + 0% { + background: transparent; + } + 50% { + background: #E4E4E4; + } + 100% { + background: transparent; + } +} + +@-moz-keyframes dl-dots { + 0% { + background: transparent; + } + 50% { + background: #E4E4E4; + } + 100% { + background: transparent; + } +} + +@-ms-keyframes dl-dots { + 0% { + background: transparent; + } + 50% { + background: #E4E4E4; + } + 100% { + background: transparent; + } +} + +@keyframes dl-dots { + 0% { + background: transparent; + } + 50% { + background: #E4E4E4; + } + 100% { + background: transparent; + } +} +*/ diff --git a/web/static/app/app.js b/web/static/app/app.js index 5274362..6fa1f71 100644 --- a/web/static/app/app.js +++ b/web/static/app/app.js @@ -11,15 +11,62 @@ ctrl.device = device.data.device; } - function PropertyController($route, device, values) { + function PropertyController($timeout, $route, DillerRpc, device, values) { var ctrl = this; ctrl.device = device.data.device; ctrl.property = _.find(ctrl.device.properties, {id: $route.current.params.propertyId}); ctrl.values = values.data.values; + + var refreshPromise; + ctrl.refresh = function () { + $timeout.cancel(refreshPromise); + refreshPromise = $timeout(function () { + ctrl.loading = true; + }, 200); + + DillerRpc.getValues($route.current.params.propertyId).then(function (res) { + ctrl.values = res.data.values; + ctrl.loading = false; + $timeout.cancel(refreshPromise); + }) + }; + } + + function TimestampFilter() { + return function (value) { + if (!value) { + return; + } + + return moment(value).startOf('second').fromNow(); + } + } + + function DlTimestampDirective() { + console.log('DlTimestampDirective', DlTimestampDirective); + return { + restrict: 'E', + scope: { + value: '=' + }, + replace: true, + template: '{{value|timestamp}}' + }; + } + + function DlDotsDirective() { + return { + restrict: 'E', + scope: { + value: '=' + }, + replace: true, + template: '...\n' + }; } - function config($routeProvider, $locationProvider) { + function config($routeProvider) { $routeProvider .when('/', { controller: FrontPageController, @@ -49,8 +96,6 @@ .otherwise({ redirectTo: '/' }); - - //$locationProvider.html5Mode(true); } function DillerConfig() { @@ -67,6 +112,9 @@ angular .module('Diller', ['ngRoute']) .config(config) + .filter('timestamp', TimestampFilter) + .directive('dlTimestamp', DlTimestampDirective) + .directive('dlDots', DlDotsDirective) .service('DillerConfig', DillerConfig) .service('DillerRpc', DillerRpc); })(); diff --git a/web/static/app/templates/property.html b/web/static/app/templates/property.html index 65a66e8..cd003ca 100644 --- a/web/static/app/templates/property.html +++ b/web/static/app/templates/property.html @@ -16,7 +16,15 @@
  • Description: {{ctrl.property.description}}
  • -

    Latest Values

    +

    + Latest Values + + refresh + + + Loading + +

    @@ -27,7 +35,7 @@ - + diff --git a/web/templates/index.jade b/web/templates/index.jade index 025354e..6dad0f5 100644 --- a/web/templates/index.jade +++ b/web/templates/index.jade @@ -14,6 +14,7 @@ html(lang='en') script(src="bower_components/angular/angular.js", type="application/javascript") script(src="bower_components/angular-route/angular-route.js", type="application/javascript") script(src="bower_components/lodash/lodash.js", type="application/javascript") + script(src="bower_components/moment/moment.js", type="application/javascript") script(src="app/DillerRpc.js", type="application/javascript") script(src="app/app.js", type="application/javascript") -- cgit v1.2.3
    {{v.timestamp | date:'medium'}} {{v.value}}