diff --git a/js/.eslintrc.json b/js/.eslintrc.json
index 0a19574..0e3cc4f 100644
--- a/js/.eslintrc.json
+++ b/js/.eslintrc.json
@@ -65,6 +65,7 @@
"vars-on-top": "off",
"operator-assignment": "off",
"no-else-return": "off",
+ "prefer-object-spread": "off",
// Node.js and CommonJS
"callback-return": "off",
diff --git a/js/beestat/date.js b/js/beestat/date.js
new file mode 100644
index 0000000..ca0d8a0
--- /dev/null
+++ b/js/beestat/date.js
@@ -0,0 +1,26 @@
+/**
+ * Format a date.
+ *
+ * @param {object} args Instructions on how to format:
+ * date (required) - Temperature to work with
+ *
+ * @return {string} The formatted date.
+ */
+beestat.date = function(args) {
+ // Allow passing a single argument of date for convenience.
+ if (typeof args !== 'object' || args === null) {
+ args = {
+ 'date': args
+ };
+ }
+
+ const m = moment(args.date);
+ if (
+ args.date !== undefined &&
+ m.isValid() === true
+ ) {
+ return m.format(beestat.setting('date_format'));
+ } else {
+ return '';
+ }
+};
diff --git a/js/beestat/setting.js b/js/beestat/setting.js
index 01fa6ff..447b7b0 100644
--- a/js/beestat/setting.js
+++ b/js/beestat/setting.js
@@ -81,7 +81,9 @@ beestat.setting = function(argument_1, opt_value, opt_callback) {
'visualize.heat_map_absolute.occupancy.max': 100,
'visualize.hide_affiliate': false,
'visualize.three_d.show_labels': false,
- 'visualize.three_d.auto_rotate': false
+ 'visualize.three_d.auto_rotate': false,
+
+ 'date_format': 'M/D/YYYY'
};
// Figure out what we're trying to do.
diff --git a/js/component.js b/js/component.js
index 5e6bc8f..cebad9f 100644
--- a/js/component.js
+++ b/js/component.js
@@ -30,8 +30,13 @@ beestat.component.prototype.render = function(parent) {
var self = this;
if (parent !== undefined) {
- this.component_container_ = $.createElement('div')
- .style('position', 'relative');
+ this.component_container_ = $.createElement('div');
+ Object.assign(this.component_container_[0].style, Object.assign(
+ {
+ 'position': 'relative'
+ },
+ this.style_
+ ));
this.decorate_(this.component_container_);
parent.appendChild(this.component_container_);
} else {
@@ -93,3 +98,16 @@ beestat.component.prototype.dispose = function() {
beestat.component.prototype.decorate_ = function() {
// Left for the subclass to implement.
};
+
+/**
+ * Add custom styling to a component container. Mostly useful for when a
+ * component needs margins, etc applied depending on the context.
+ *
+ * @param {object} style
+ *
+ * @return {beestat.component} This
+ */
+beestat.component.prototype.style = function(style) {
+ this.style_ = style;
+ return this.rerender();
+};
diff --git a/js/component/card/three_d.js b/js/component/card/three_d.js
index a920ec0..1452970 100644
--- a/js/component/card/three_d.js
+++ b/js/component/card/three_d.js
@@ -190,10 +190,10 @@ beestat.component.card.three_d.prototype.decorate_contents_ = function(parent) {
}
} else {
required_begin = moment(
- beestat.setting('visualize.range_static_begin') + ' 00:00:00'
+ beestat.setting('visualize.range_static.begin') + ' 00:00:00'
);
required_end = moment(
- beestat.setting('visualize.range_static_end') + ' 23:59:59'
+ beestat.setting('visualize.range_static.end') + ' 23:59:59'
);
}
@@ -379,7 +379,7 @@ beestat.component.card.three_d.prototype.decorate_drawing_pane_ = function(paren
}
} else {
this.date_m_ = moment(
- beestat.setting('visualize.range_static_begin') + ' 00:00:00'
+ beestat.setting('visualize.range_static.begin') + ' 00:00:00'
);
}
diff --git a/js/component/card/visualize_settings.js b/js/component/card/visualize_settings.js
index f50c4e1..1f75a96 100644
--- a/js/component/card/visualize_settings.js
+++ b/js/component/card/visualize_settings.js
@@ -21,34 +21,34 @@ beestat.extend(beestat.component.card.visualize_settings, beestat.component.card
* @param {rocket.Elements} parent
*/
beestat.component.card.visualize_settings.prototype.decorate_contents_ = function(parent) {
- const grid_1 = document.createElement('div');
- Object.assign(grid_1.style, {
+ const grid = document.createElement('div');
+ Object.assign(grid.style, {
'display': 'grid',
'grid-template-columns': 'repeat(auto-fit, minmax(min(350px, 100%), 1fr))',
'grid-gap': `${beestat.style.size.gutter}px`,
'margin-bottom': `${beestat.style.size.gutter}px`
});
- parent.appendChild(grid_1);
+ parent.appendChild(grid);
- const type_container = document.createElement('div');
- this.decorate_data_type_(type_container);
- grid_1.appendChild(type_container);
+ const left_container = document.createElement('div');
+ grid.appendChild(left_container);
+ const right_container = document.createElement('div');
+ grid.appendChild(right_container);
- const time_period_container = document.createElement('div');
- this.decorate_time_period_(time_period_container);
- grid_1.appendChild(time_period_container);
-
- const grid_2 = document.createElement('div');
- Object.assign(grid_2.style, {
- 'display': 'grid',
- 'grid-template-columns': 'repeat(auto-fit, minmax(min(350px, 100%), 1fr))',
- 'grid-gap': `${beestat.style.size.gutter}px`
+ const data_type_container = document.createElement('div');
+ Object.assign(data_type_container.style, {
+ 'margin-bottom': `${beestat.style.size.gutter}px`
});
- parent.appendChild(grid_2);
+ this.decorate_data_type_(data_type_container);
+ left_container.appendChild(data_type_container);
const heat_map_values_container = document.createElement('div');
this.decorate_heat_map_values_(heat_map_values_container);
- grid_2.appendChild(heat_map_values_container);
+ left_container.appendChild(heat_map_values_container);
+
+ const time_period_container = document.createElement('div');
+ this.decorate_time_period_(time_period_container);
+ right_container.appendChild(time_period_container);
// If at least one sensor is on the floor plan and the data is loading.
if (
@@ -121,18 +121,27 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_values_ =
const types = [
{
'code': 'relative',
- 'name': 'Relative',
+ 'name': 'Dynamic',
'icon': 'arrow_expand_horizontal'
},
{
'code': 'absolute',
- 'name': 'Absolute',
+ 'name': 'Static',
'icon': 'arrow_horizontal_lock'
}
];
+ const container = document.createElement('div');
+ Object.assign(container.style, {
+ 'display': 'flex',
+ 'flex-wrap': 'wrap',
+ 'grid-gap': `${beestat.style.size.gutter}px`
+ });
+ parent.appendChild(container);
+
const color = beestat.style.color.orange.base;
const tile_group = new beestat.component.tile_group();
+
types.forEach(function(type) {
const tile = new beestat.component.tile()
.set_background_hover_color(color)
@@ -152,12 +161,11 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_values_ =
}
tile_group.add_tile(tile);
});
- tile_group.render($(parent));
+ tile_group.render($(container));
if (beestat.setting('visualize.heat_map_values') === 'absolute') {
const min_max_container = document.createElement('div');
- min_max_container.style.marginTop = `${beestat.style.size.gutter}px`;
- parent.appendChild(min_max_container);
+ container.appendChild(min_max_container);
let type;
let inputmode;
@@ -249,7 +257,7 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_values_ =
span = document.createElement('span');
span.style.display = 'inline-block';
min.render($(span));
- parent.appendChild(span);
+ min_max_container.appendChild(span);
span = document.createElement('span');
span.innerText = 'to';
@@ -258,12 +266,12 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_values_ =
'margin-left': `${beestat.style.size.gutter}px`,
'margin-right': `${beestat.style.size.gutter}px`
});
- parent.appendChild(span);
+ min_max_container.appendChild(span);
span = document.createElement('span');
span.style.display = 'inline-block';
max.render($(span));
- parent.appendChild(span);
+ min_max_container.appendChild(span);
span = document.createElement('span');
switch (beestat.setting('visualize.data_type')) {
@@ -279,7 +287,7 @@ beestat.component.card.visualize_settings.prototype.decorate_heat_map_values_ =
'display': 'inline-block',
'margin-left': `${beestat.style.size.gutter}px`
});
- parent.appendChild(span);
+ min_max_container.appendChild(span);
}
};
@@ -295,7 +303,7 @@ beestat.component.card.visualize_settings.prototype.decorate_time_period_ = func
const color = beestat.style.color.purple.base;
- const tile_group = new beestat.component.tile_group();
+ const tile_group_dynamic = new beestat.component.tile_group();
// Current Day
const current_day_tile = new beestat.component.tile()
@@ -313,13 +321,15 @@ beestat.component.card.visualize_settings.prototype.decorate_time_period_ = func
current_day_tile
.set_background_color(beestat.style.color.bluegray.light)
.addEventListener('click', function() {
- beestat.setting('visualize.range_type', 'dynamic');
- beestat.setting('visualize.range_dynamic', 0);
+ beestat.setting({
+ 'visualize.range_type': 'dynamic',
+ 'visualize.range_dynamic': 0
+ });
beestat.cache.delete('data.three_d__runtime_sensor');
self.rerender();
});
}
- tile_group.add_tile(current_day_tile);
+ tile_group_dynamic.add_tile(current_day_tile);
// Yesterday
const yesterday_tile = new beestat.component.tile()
@@ -337,13 +347,15 @@ beestat.component.card.visualize_settings.prototype.decorate_time_period_ = func
yesterday_tile
.set_background_color(beestat.style.color.bluegray.light)
.addEventListener('click', function() {
- beestat.setting('visualize.range_type', 'dynamic');
- beestat.setting('visualize.range_dynamic', 1);
+ beestat.setting({
+ 'visualize.range_type': 'dynamic',
+ 'visualize.range_dynamic': 1
+ });
beestat.cache.delete('data.three_d__runtime_sensor');
self.rerender();
});
}
- tile_group.add_tile(yesterday_tile);
+ tile_group_dynamic.add_tile(yesterday_tile);
// Current Week
const week_tile = new beestat.component.tile()
@@ -361,37 +373,65 @@ beestat.component.card.visualize_settings.prototype.decorate_time_period_ = func
week_tile
.set_background_color(beestat.style.color.bluegray.light)
.addEventListener('click', function() {
- beestat.setting('visualize.range_type', 'dynamic');
- beestat.setting('visualize.range_dynamic', 7);
+ beestat.setting({
+ 'visualize.range_type': 'dynamic',
+ 'visualize.range_dynamic': 7
+ });
beestat.cache.delete('data.three_d__runtime_sensor');
self.rerender();
});
}
- tile_group.add_tile(week_tile);
+ tile_group_dynamic.add_tile(week_tile);
// Custom
-/* const custom_tile = new beestat.component.tile()
+ const tile_group_static = new beestat.component.tile_group();
+ const custom_tile = new beestat.component.tile()
.set_background_hover_color(color)
.set_text_color('#fff')
.set_icon('calendar_edit')
.set_text('Custom');
- if (
- beestat.setting('visualize.range_type') === 'static'
- ) {
+ custom_tile
+ .addEventListener('click', function() {
+ new beestat.component.modal.visualize_custom().render();
+ });
+
+ if (beestat.setting('visualize.range_type') === 'static') {
custom_tile.set_background_color(color);
} else {
- custom_tile
- .set_background_color(beestat.style.color.bluegray.light)
- .addEventListener('click', function() {
- // TODO MODAL
- beestat.setting('visualize.range_type', 'static');
- self.rerender();
- });
+ custom_tile.set_background_color(beestat.style.color.bluegray.light);
}
- tile_group.add_tile(custom_tile);*/
+ tile_group_static.add_tile(custom_tile);
- tile_group.render($(parent));
+ // Static range
+ if (beestat.setting('visualize.range_type') === 'static') {
+ const static_range_tile = new beestat.component.tile()
+ .set_shadow(false)
+ .set_text(
+ beestat.date(beestat.setting('visualize.range_static.begin')) +
+ ' to ' +
+ beestat.date(beestat.setting('visualize.range_static.end'))
+ );
+ tile_group_static.add_tile(static_range_tile);
+
+ const static_range_edit_tile = new beestat.component.tile()
+ .set_background_color(beestat.style.color.bluegray.light)
+ .set_background_hover_color(color)
+ .set_text_color('#fff')
+ .set_icon('pencil');
+ static_range_edit_tile
+ .addEventListener('click', function() {
+ new beestat.component.modal.visualize_custom().render();
+ });
+ tile_group_static.add_tile(static_range_edit_tile);
+ }
+
+ tile_group_dynamic
+ .style({
+ 'margin-bottom': `${beestat.style.size.gutter}px`
+ })
+ .render($(parent));
+ tile_group_static.render($(parent));
};
/**
diff --git a/js/component/input/text.js b/js/component/input/text.js
index 04c7220..54b6de4 100644
--- a/js/component/input/text.js
+++ b/js/component/input/text.js
@@ -32,6 +32,11 @@ beestat.component.input.text = function() {
) / 10 ** self.transform_.decimals;
}
break;
+ case 'date':
+ self.input_.value = moment(self.input_.value).format(
+ beestat.setting('date_format')
+ )
+ break;
}
}
@@ -59,9 +64,7 @@ beestat.component.input.text.prototype.decorate_ = function(parent) {
'padding': `${beestat.style.size.gutter / 2}px`,
'color': '#ffffff',
'outline': 'none',
- 'transition': 'background 200ms ease',
- 'margin-bottom': `${beestat.style.size.gutter}px`,
- 'border-bottom': `2px solid ${beestat.style.color.lightblue.base}`
+ 'transition': 'background 200ms ease'
});
// Set input width; interpret string widths literally (ex: 100%)
diff --git a/js/component/modal/create_floor_plan.js b/js/component/modal/create_floor_plan.js
index 2d152b6..94f3fba 100644
--- a/js/component/modal/create_floor_plan.js
+++ b/js/component/modal/create_floor_plan.js
@@ -287,7 +287,7 @@ beestat.component.modal.create_floor_plan.prototype.get_buttons_ = function() {
beestat.component.modal.create_floor_plan.prototype.decorate_error_ = function(parent) {
let has_error = false;
- var div = $.createElement('div').style({
+ const div = $.createElement('div').style({
'background': beestat.style.color.red.base,
'color': '#fff',
'border-radius': beestat.style.size.border_radius,
diff --git a/js/component/modal/runtime_sensor_detail_custom.js b/js/component/modal/runtime_sensor_detail_custom.js
index 9b4f269..3b1760a 100644
--- a/js/component/modal/runtime_sensor_detail_custom.js
+++ b/js/component/modal/runtime_sensor_detail_custom.js
@@ -22,7 +22,7 @@ beestat.extend(beestat.component.modal.runtime_sensor_detail_custom, beestat.com
* @param {rocket.Elements} parent
*/
beestat.component.modal.runtime_sensor_detail_custom.prototype.decorate_contents_ = function(parent) {
- parent.appendChild($.createElement('p').innerHTML('Choose a custom range to display on the Sensor Detail chart. Max range is 7 days at a time and 30 days in the past. This limit will be raised in the future.'));
+ parent.appendChild($.createElement('p').innerHTML('Choose a custom range to display on the Sensor Detail chart. Max range is 7 days at a time and 3 months in the past.'));
this.decorate_range_type_(parent);
@@ -99,7 +99,7 @@ beestat.component.modal.runtime_sensor_detail_custom.prototype.decorate_range_st
var thermostat = beestat.cache.thermostat[beestat.setting('thermostat_id')];
var min = moment.max(
moment(thermostat.sync_begin),
- moment().subtract(1, 'month')
+ moment().subtract(3, 'month')
);
var max = moment(thermostat.sync_end);
diff --git a/js/component/modal/visualize_custom.js b/js/component/modal/visualize_custom.js
new file mode 100644
index 0000000..ad18e4e
--- /dev/null
+++ b/js/component/modal/visualize_custom.js
@@ -0,0 +1,240 @@
+/**
+ * Custom date range for the Runtime Detail chart.
+ */
+beestat.component.modal.visualize_custom = function() {
+ beestat.component.modal.apply(this, arguments);
+
+ this.state_.range_begin = beestat.setting('visualize.range_static.begin');
+ this.state_.range_end = beestat.setting('visualize.range_static.end');
+
+ this.state_.error = {
+ 'range_diff_invalid': {
+ 'triggered': false,
+ 'message': 'Max range is seven days'
+ },
+ 'range_begin_invalid': {
+ 'triggered': false,
+ 'message': 'Begin date is invalid'
+ },
+ 'range_end_invalid': {
+ 'triggered': false,
+ 'message': 'End date is invalid'
+ }
+ };
+};
+beestat.extend(beestat.component.modal.visualize_custom, beestat.component.modal);
+
+/**
+ * Decorate.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.modal.visualize_custom.prototype.decorate_contents_ = function(parent) {
+ const instructions_container = document.createElement('p');
+ instructions_container.innerText = 'Choose a date range of up to seven days. Multi-day ranges will be be averaged into a single 24-hour span.';
+ parent.appendChild(instructions_container);
+
+ this.decorate_inputs_(parent[0]);
+
+ if (this.has_error_() === true) {
+ this.decorate_error_(parent[0]);
+ }
+};
+
+/**
+ * Decorate the static range inputs.
+ *
+ * @param {HTMLDivElement} parent
+ */
+beestat.component.modal.visualize_custom.prototype.decorate_inputs_ = function(parent) {
+ const self = this;
+
+ const container = document.createElement('div');
+ Object.assign(container.style, {
+ 'display': 'flex',
+ 'grid-gap': `${beestat.style.size.gutter}px`,
+ 'align-items': 'center'
+ });
+ parent.appendChild(container);
+
+ // Range begin
+ this.range_begin_input_ = new beestat.component.input.text()
+ .set_width(110)
+ .set_maxlength(10)
+ .set_requirements({
+ 'required': true,
+ 'type': 'date'
+ })
+ .set_transform({
+ 'type': 'date'
+ })
+ .set_icon('calendar')
+ .set_value(
+ beestat.date(this.state_.range_begin)
+ )
+ .render($(container));
+
+ this.range_begin_input_.addEventListener('blur', function() {
+ self.state_.range_begin = this.get_value();
+ });
+
+ // To
+ const to = document.createElement('div');
+ to.innerText = 'to';
+ container.appendChild(to);
+
+ // Range end
+ this.range_end_input_ = new beestat.component.input.text()
+ .set_width(110)
+ .set_maxlength(10)
+ .set_requirements({
+ 'required': true,
+ 'type': 'date'
+ })
+ .set_transform({
+ 'type': 'date'
+ })
+ .set_icon('calendar')
+ .set_value(
+ beestat.date(this.state_.range_end)
+ )
+ .render($(container));
+
+ this.range_end_input_.addEventListener('blur', function() {
+ self.state_.range_end = this.get_value();
+ });
+};
+
+/**
+ * Decorate the error area.
+ *
+ * @param {HTMLDivElement} parent
+ */
+beestat.component.modal.visualize_custom.prototype.decorate_error_ = function(parent) {
+ const container = document.createElement('div');
+ Object.assign(container.style, {
+ 'background': beestat.style.color.red.base,
+ 'color': '#fff',
+ 'border-radius': `${beestat.style.size.border_radius}px`,
+ 'padding': `${beestat.style.size.gutter}px`,
+ 'margin-top': `${beestat.style.size.gutter}px`
+ });
+ parent.appendChild(container);
+
+ for (let key in this.state_.error) {
+ if (this.state_.error[key].triggered === true) {
+ const error_div = document.createElement('div');
+ error_div.innerText = this.state_.error[key].message;
+ container.appendChild(error_div);
+ }
+ }
+};
+
+/**
+ * Check and see whether not there is currently an error.
+ *
+ * @return {boolean}
+ */
+beestat.component.modal.visualize_custom.prototype.has_error_ = function() {
+ this.check_error_();
+
+ for (let key in this.state_.error) {
+ if (this.state_.error[key].triggered === true) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Check to see if there are any errors and update the state.
+ */
+beestat.component.modal.visualize_custom.prototype.check_error_ = function() {
+ this.state_.error.range_begin_invalid.triggered =
+ !this.range_begin_input_.meets_requirements();
+
+ this.state_.error.range_end_invalid.triggered =
+ !this.range_end_input_.meets_requirements();
+
+ this.state_.error.range_diff_invalid.triggered = false;
+ if (
+ this.range_begin_input_.meets_requirements() === true &&
+ this.range_end_input_.meets_requirements() === true
+ ) {
+ const range_begin_m = moment(this.range_begin_input_.get_value());
+ const range_end_m = moment(this.range_end_input_.get_value());
+
+ if (Math.abs(range_begin_m.diff(range_end_m, 'day')) > 7) {
+ this.state_.error.range_diff_invalid.triggered = true;
+ }
+ }
+};
+
+/**
+ * Get title.
+ *
+ * @return {string} Title
+ */
+beestat.component.modal.visualize_custom.prototype.get_title_ = function() {
+ return 'Visualize - Custom Range';
+};
+
+/**
+ * Get the buttons that go on the bottom of this modal.
+ *
+ * @return {[beestat.component.button]} The buttons.
+ */
+beestat.component.modal.visualize_custom.prototype.get_buttons_ = function() {
+ var self = this;
+
+ var cancel = new beestat.component.tile()
+ .set_background_color('#fff')
+ .set_text_color(beestat.style.color.gray.base)
+ .set_text_hover_color(beestat.style.color.red.base)
+ .set_shadow(false)
+ .set_text('Cancel')
+ .addEventListener('click', function() {
+ self.dispose();
+ });
+
+ const save = new beestat.component.tile()
+ .set_background_color(beestat.style.color.green.base)
+ .set_background_hover_color(beestat.style.color.green.light)
+ .set_text_color('#fff')
+ .set_text('Save')
+ .addEventListener('click', function() {
+ this
+ .set_background_color(beestat.style.color.gray.base)
+ .set_background_hover_color()
+ .removeEventListener('click');
+
+ if (self.has_error_() === true) {
+ self.rerender();
+ } else {
+ if (moment(self.state_.range_begin).isAfter(moment(self.state_.range_end)) === true) {
+ var temp = self.state_.range_begin;
+ self.state_.range_begin = self.state_.range_end;
+ self.state_.range_end = temp;
+ }
+
+ beestat.cache.delete('data.three_d__runtime_sensor');
+ beestat.setting(
+ {
+ 'visualize.range_type': 'static',
+ 'visualize.range_static.begin': self.state_.range_begin,
+ 'visualize.range_static.end': self.state_.range_end
+ },
+ undefined,
+ function() {
+ self.dispose();
+ }
+ );
+ }
+ });
+
+ return [
+ cancel,
+ save
+ ];
+};
diff --git a/js/js.php b/js/js.php
index 539c4e7..b94d290 100755
--- a/js/js.php
+++ b/js/js.php
@@ -47,6 +47,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
// Layer
echo '' . PHP_EOL;
@@ -127,6 +128,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
+ echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;
echo '' . PHP_EOL;