diff --git a/js/beestat.js b/js/beestat.js
index 931d323..b5a988c 100644
--- a/js/beestat.js
+++ b/js/beestat.js
@@ -52,36 +52,9 @@ if ('serviceWorker' in navigator) {
}
/**
- * Dispatch a breakpoint event every time a browser resize crosses one of the
- * breakpoints. Typically a component will use this event to rerender itself
- * when CSS breakpoints are not feasible or appropriate.
+ * Dispatch the resize event every now and then.
*/
-beestat.width = window.innerWidth;
window.addEventListener('resize', rocket.throttle(100, function() {
- var breakpoints = [
- 600,
- 650,
- 800,
- 850,
- 1000
- ];
-
- breakpoints.forEach(function(breakpoint) {
- if (
- (
- beestat.width > breakpoint &&
- window.innerWidth <= breakpoint
- ) ||
- (
- beestat.width < breakpoint &&
- window.innerWidth >= breakpoint
- )
- ) {
- beestat.width = window.innerWidth;
- beestat.dispatcher.dispatchEvent('breakpoint');
- }
- });
-
beestat.dispatcher.dispatchEvent('resize');
}));
diff --git a/js/beestat/text_dimensions.js b/js/beestat/text_dimensions.js
new file mode 100644
index 0000000..ccb3d7c
--- /dev/null
+++ b/js/beestat/text_dimensions.js
@@ -0,0 +1,30 @@
+/**
+ * Get the dimensions of a text string.
+ *
+ * @param {string} text
+ * @param {number} font_size
+ * @param {number} font_weight
+ *
+ * @return {number}
+ */
+beestat.text_dimensions = function(text, font_size, font_weight) {
+ const div = document.createElement('div');
+ div.style.fontSize = font_size + 'px';
+ div.style.fontWeight = font_weight;
+ div.style.position = 'absolute';
+ div.style.left = -1000;
+ div.style.top = -1000;
+
+ div.textContent = text;
+
+ document.body.appendChild(div);
+
+ const bounding_box = div.getBoundingClientRect();
+
+ document.body.removeChild(div);
+
+ return {
+ 'width': bounding_box.width,
+ 'height': bounding_box.height
+ };
+};
diff --git a/js/component.js b/js/component.js
index cebad9f..551cce2 100644
--- a/js/component.js
+++ b/js/component.js
@@ -8,8 +8,8 @@ beestat.component = function() {
this.layer_ = beestat.current_layer;
- if (this.rerender_on_breakpoint_ === true) {
- beestat.dispatcher.addEventListener('breakpoint', function() {
+ if (this.rerender_on_resize_ === true) {
+ beestat.dispatcher.addEventListener('resize', function() {
self.rerender();
});
}
diff --git a/js/component/card/air_quality_summary.js b/js/component/card/air_quality_summary.js
index 316a639..236b4ab 100644
--- a/js/component/card/air_quality_summary.js
+++ b/js/component/card/air_quality_summary.js
@@ -35,7 +35,7 @@ beestat.component.card.air_quality_summary = function(thermostat_id) {
};
beestat.extend(beestat.component.card.air_quality_summary, beestat.component.card);
-beestat.component.card.air_quality_summary.prototype.rerender_on_breakpoint_ = true;
+beestat.component.card.air_quality_summary.prototype.rerender_on_resize_ = true;
/**
* Decorate
@@ -245,7 +245,7 @@ beestat.component.card.air_quality_summary.prototype.decorate_chart_ = function(
tr.appendChild(
$.createElement('td')
- .innerText(new_hour + (beestat.width > 700 ? meridiem : ''))
+ .innerText(new_hour + (window.innerWidth > 700 ? meridiem : ''))
.style({
'text-align': 'center',
'font-size': beestat.style.font_size.small
diff --git a/js/component/card/metrics.js b/js/component/card/metrics.js
index 06b3a76..c3df250 100644
--- a/js/component/card/metrics.js
+++ b/js/component/card/metrics.js
@@ -30,7 +30,7 @@ beestat.component.card.metrics = function(thermostat_id) {
};
beestat.extend(beestat.component.card.metrics, beestat.component.card);
-beestat.component.card.metrics.prototype.rerender_on_breakpoint_ = true;
+beestat.component.card.metrics.prototype.rerender_on_resize_ = true;
/**
* Decorate
@@ -64,9 +64,9 @@ beestat.component.card.metrics.prototype.decorate_contents_ = function(parent) {
this.decorate_empty_(parent);
} else {
let column_count = 1;
- if (beestat.width > 1000) {
+ if (window.innerWidth > 1000) {
column_count = 3;
- } else if (beestat.width > 800) {
+ } else if (window.innerWidth > 800) {
column_count = 2;
}
const column_span = 12 / column_count;
diff --git a/js/component/chart.js b/js/component/chart.js
index 2c3681c..61fe72d 100644
--- a/js/component/chart.js
+++ b/js/component/chart.js
@@ -661,7 +661,7 @@ beestat.component.chart.prototype.tooltip_formatter_helper_ = function(title, se
beestat.component.chart.prototype.get_dock_tooltip_ = function() {
return (
beestat.setting('ui.always_dock_tooltips') === true ||
- beestat.width < 600
+ window.innerWidth < 600
);
};
diff --git a/js/component/header.js b/js/component/header.js
index 2baa458..b4d7f0b 100644
--- a/js/component/header.js
+++ b/js/component/header.js
@@ -4,7 +4,7 @@
* @param {string} active_layer The currently active layer.
*/
beestat.component.header = function(active_layer) {
- var self = this;
+ const self = this;
this.active_layer_ = active_layer;
@@ -19,12 +19,170 @@ beestat.component.header = function(active_layer) {
};
beestat.extend(beestat.component.header, beestat.component);
-beestat.component.header.prototype.rerender_on_breakpoint_ = true;
+beestat.component.header.prototype.rerender_on_resize_ = true;
+/**
+ * Decorate.
+ *
+ * @param {rocket.Elements} parent
+ */
beestat.component.header.prototype.decorate_ = function(parent) {
- var self = this;
+ // Define base widths for every part of the header at different sizes.
+ const switcher_width = this.get_switcher_width_();
+ this.dimensions_ = {
+ 'large': {
+ 'logo': 160,
+ 'navigation': 565,
+ 'switcher': switcher_width,
+ 'menu': 50,
+ 'right_margin': 16
+ },
+ 'medium': {
+ 'logo': 160,
+ 'navigation': 225,
+ 'switcher': switcher_width,
+ 'menu': 50,
+ 'right_margin': 16
+ },
+ 'small': {
+ 'logo': 55,
+ 'navigation': 225,
+ 'switcher': switcher_width,
+ 'menu': 50,
+ 'right_margin': 16
+ }
+ };
- var pages = [
+ /**
+ * Figure out which configuration will fit, preferring the largest first
+ * with the switcher, then without the switcher. Same pattern as we get
+ * smaller.
+ */
+ if (
+ window.innerWidth >= (
+ this.dimensions_.large.logo +
+ this.dimensions_.large.navigation +
+ this.dimensions_.large.switcher +
+ this.dimensions_.large.menu +
+ this.dimensions_.large.right_margin
+ )
+ ) {
+ this.dimension_ = 'large';
+ this.switcher_enabled_ = true;
+ } else if (
+ window.innerWidth >= (
+ this.dimensions_.large.logo +
+ this.dimensions_.large.navigation +
+ this.dimensions_.large.menu +
+ this.dimensions_.large.right_margin
+ )
+ ) {
+ this.dimension_ = 'large';
+ this.switcher_enabled_ = false;
+ } else if (
+ window.innerWidth >= (
+ this.dimensions_.medium.logo +
+ this.dimensions_.medium.navigation +
+ this.dimensions_.medium.switcher +
+ this.dimensions_.medium.menu +
+ this.dimensions_.medium.right_margin
+ )
+ ) {
+ this.dimension_ = 'medium';
+ this.switcher_enabled_ = true;
+ } else if (
+ window.innerWidth >= (
+ this.dimensions_.medium.logo +
+ this.dimensions_.medium.navigation +
+ this.dimensions_.medium.menu +
+ this.dimensions_.medium.right_margin
+ )
+ ) {
+ this.dimension_ = 'medium';
+ this.switcher_enabled_ = false;
+ } else if (
+ window.innerWidth >= (
+ this.dimensions_.small.logo +
+ this.dimensions_.small.navigation +
+ this.dimensions_.small.switcher +
+ this.dimensions_.small.menu +
+ this.dimensions_.small.right_margin
+ )
+ ) {
+ this.dimension_ = 'small';
+ this.switcher_enabled_ = true;
+ } else if (
+ window.innerWidth >= (
+ this.dimensions_.small.logo +
+ this.dimensions_.small.navigation +
+ this.dimensions_.small.menu +
+ this.dimensions_.small.right_margin
+ )
+ ) {
+ this.dimension_ = 'small';
+ this.switcher_enabled_ = false;
+ }
+
+ // Decorate all the parts into a flex row.
+ const row = $.createElement('div').style({
+ 'display': 'flex',
+ 'align-items': 'center',
+ 'flex-grow': '1',
+ 'margin': '-' + (beestat.style.size.gutter / 2) + 'px 0 ' + (beestat.style.size.gutter / 4) + 'px -' + beestat.style.size.gutter + 'px'
+ });
+
+ this.decorate_logo_(row);
+ this.decorate_navigation_(row);
+ if (this.switcher_enabled_ === true) {
+ this.decorate_switcher_(row);
+ }
+ this.decorate_menu_(row);
+
+ parent.appendChild(row);
+};
+
+/**
+ * Decorate the logo.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.header.prototype.decorate_logo_ = function(parent) {
+ const column = $.createElement('div')
+ .style({
+ 'flex': '0 0 ' + this.dimensions_[this.dimension_].logo + 'px',
+ 'padding': beestat.style.size.gutter + 'px 0 0 ' + beestat.style.size.gutter + 'px'
+ });
+
+ if (this.dimension_ === 'medium' || this.dimension_ === 'large') {
+ column.style({
+ 'margin': '8px 0 4px 0'
+ });
+ (new beestat.component.logo(32)).render(column);
+ } else {
+ // column.style({'flex': '0 0 ' + dimensions[dimension].logo + 'px'});
+ const img = $.createElement('img')
+ .setAttribute('src', '/favicon.png')
+ .style({
+ 'width': '32px',
+ 'height': '32px',
+ 'margin-top': '11px',
+ 'margin-bottom': '6px'
+ });
+ column.appendChild(img);
+ }
+
+ parent.appendChild(column);
+};
+
+/**
+ * Decorate the navigation buttons.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.header.prototype.decorate_navigation_ = function(parent) {
+ const self = this;
+
+ const pages = [
{
'layer': 'detail',
'text': 'Detail',
@@ -52,55 +210,29 @@ beestat.component.header.prototype.decorate_ = function(parent) {
}
];
- pages.push();
-
- var gutter = beestat.style.size.gutter;
-
- var row = $.createElement('div').style({
- 'display': 'flex',
- 'align-items': 'center',
- 'flex-grow': '1',
- 'margin': '-' + (gutter / 2) + 'px 0 ' + (gutter / 4) + 'px -' + gutter + 'px'
+ const column = $.createElement('div').style({
+ 'padding': beestat.style.size.gutter + 'px 0 0 ' + beestat.style.size.gutter + 'px'
});
- parent.appendChild(row);
- // Logo
- var column_logo = $.createElement('div').style({'padding': gutter + 'px 0 0 ' + gutter + 'px'});
- row.appendChild(column_logo);
- if (beestat.width > 600) {
- column_logo.style({
- 'flex': '0 0 160px',
- 'margin': '8px 0 4px 0'
+ // If the swithcer is enabled, that takes up extra space. If not, this does.
+ if (this.switcher_enabled_ === true) {
+ column.style({
+ 'flex': '0 0 ' + this.dimensions_[this.dimension_].navigation + 'px'
});
- (new beestat.component.logo(32)).render(column_logo);
} else {
- column_logo.style({'flex': '0 0 32px'});
- var img = $.createElement('img')
- .setAttribute('src', '/favicon.png')
- .style({
- 'width': '32px',
- 'height': '32px',
- 'margin-top': '11px',
- 'margin-bottom': '6px'
- });
- column_logo.appendChild(img);
+ column.style({
+ 'flex': '1'
+ });
}
- // Navigation
- var column_navigation = $.createElement('div').style({
- 'flex': '1',
- 'padding': gutter + 'px 0 0 ' + gutter + 'px'
- });
- row.appendChild(column_navigation);
-
- var tile_group = new beestat.component.tile_group();
+ const tile_group = new beestat.component.tile_group();
pages.forEach(function(page) {
- var button = new beestat.component.tile()
+ const button = new beestat.component.tile()
.set_icon(page.icon)
.set_shadow(false)
.set_text_color(beestat.style.color.bluegray.dark);
- if (beestat.width > 850) {
+ if (self.dimension_ === 'large') {
button.set_text(page.text);
}
@@ -122,29 +254,120 @@ beestat.component.header.prototype.decorate_ = function(parent) {
tile_group.add_tile(button);
});
- tile_group.render(column_navigation);
+ tile_group.render(column);
+ parent.appendChild(column);
+};
+
+/**
+ * Decorate the thermostat switcher.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.header.prototype.decorate_switcher_ = function(parent) {
+ const column = $.createElement('div').style({
+ 'flex': '1',
+ 'padding': beestat.style.size.gutter + 'px 0 0 ' + beestat.style.size.gutter + 'px',
+ 'text-align': 'right'
+ });
+
+ const change_thermostat_tile_group = new beestat.component.tile_group();
+
+ const sorted_thermostats = $.values(beestat.cache.thermostat)
+ .sort(function(a, b) {
+ return a.name > b.name;
+ });
+
+ sorted_thermostats.forEach(function(thermostat) {
+ if (thermostat.thermostat_id !== beestat.setting('thermostat_id')) {
+ const change_thermostat_tile = new beestat.component.tile.thermostat.switcher(thermostat.thermostat_id)
+ .set_size('medium')
+ .set_text_color('#fff')
+ .set_background_color(beestat.style.color.bluegray.base)
+ .set_background_hover_color('#fff')
+ .set_text_hover_color(beestat.style.color.bluegray.dark)
+ .addEventListener('click', function() {
+ beestat.setting('thermostat_id', thermostat.thermostat_id, function() {
+ window.location.reload();
+ });
+ });
+
+ change_thermostat_tile_group.add_tile(change_thermostat_tile);
+ }
+ });
+
+ change_thermostat_tile_group.render(column);
+
+ parent.appendChild(column);
+};
+
+/**
+ * Get the width of the thermostat switcher box. This could change due to any
+ * number of factors, but it should more or less work.
+ *
+ * @return {number} Width in pixels.
+ */
+beestat.component.header.prototype.get_switcher_width_ = function() {
+ let width = 0;
+
+ const sorted_thermostats = $.values(beestat.cache.thermostat)
+ .sort(function(a, b) {
+ return a.name > b.name;
+ });
+
+ sorted_thermostats.forEach(function(thermostat) {
+ if (thermostat.thermostat_id !== beestat.setting('thermostat_id')) {
+ const change_thermostat_tile = new beestat.component.tile.thermostat.switcher(
+ thermostat.thermostat_id
+ );
+ const text_dimensions = beestat.text_dimensions(
+ change_thermostat_tile.get_text_(),
+ 13,
+ 300
+ );
+
+ width += text_dimensions.width;
+
+ // Left/right padding on the button 8+8=16
+ width += 16;
+
+ // Left margin between buttons
+ width += 8;
+ }
+ });
+
+ // Left padding on the column
+ width += 16;
+
+ return width;
+};
+
+/**
+ * Decorate the menu.
+ *
+ * @param {rocket.Elements} parent
+ */
+beestat.component.header.prototype.decorate_menu_ = function(parent) {
// Menu
-
- var last_read_announcement_id = beestat.setting('last_read_announcement_id');
- var unread_announcement_count = Object.keys(beestat.cache.announcement)
+ const last_read_announcement_id = beestat.setting('last_read_announcement_id');
+ const unread_announcement_count = Object.keys(beestat.cache.announcement)
.filter(function(announcement_id) {
return announcement_id > last_read_announcement_id;
}).length;
- var column_menu = $.createElement('div').style({
- 'flex': '0 0 50px',
- 'padding': gutter + 'px 0 0 ' + gutter + 'px',
+ const column = $.createElement('div').style({
+ 'flex': '0 0 ' + this.dimensions_[this.dimension_].menu + 'px',
+ 'padding': beestat.style.size.gutter + 'px 0 0 ' + beestat.style.size.gutter + 'px',
'text-align': 'right'
});
- row.appendChild(column_menu);
- var menu = new beestat.component.menu();
+
+ const menu = new beestat.component.menu();
if (unread_announcement_count > 0) {
menu
.set_bubble_text(unread_announcement_count)
.set_bubble_color(beestat.style.color.red.base);
}
- menu.render(column_menu);
+ menu.render(column);
if (Object.keys(beestat.cache.ecobee_thermostat).length > 1) {
menu.add_menu_item(new beestat.component.menu_item()
@@ -164,7 +387,7 @@ beestat.component.header.prototype.decorate_ = function(parent) {
}));
}
- var announcements_menu_item = new beestat.component.menu_item()
+ const announcements_menu_item = new beestat.component.menu_item()
.set_text('Announcements')
.set_icon('bullhorn')
.set_callback(function() {
@@ -211,4 +434,7 @@ beestat.component.header.prototype.decorate_ = function(parent) {
window.beestat_api_key_local
);
}));
+
+ parent.appendChild(column);
};
+
diff --git a/js/component/modal/change_thermostat.js b/js/component/modal/change_thermostat.js
index 02ea7f0..a119b5c 100644
--- a/js/component/modal/change_thermostat.js
+++ b/js/component/modal/change_thermostat.js
@@ -34,6 +34,7 @@ beestat.component.modal.change_thermostat.prototype.decorate_contents_ = functio
grid.appendChild(div);
const tile = new beestat.component.tile.thermostat(thermostat.thermostat_id)
+ .set_size('large')
.set_text_color('#fff')
.set_display('block');
diff --git a/js/component/tile.js b/js/component/tile.js
index e6ff42d..8cea68b 100644
--- a/js/component/tile.js
+++ b/js/component/tile.js
@@ -75,6 +75,13 @@ beestat.component.tile.prototype.decorate_ = function(parent) {
});
}
+ // Width override
+ if (this.width_ !== undefined) {
+ Object.assign(this.container_.style, {
+ 'width': `${this.width_}px`
+ });
+ }
+
// Tabbable
if (tabbable === true) {
this.container_.setAttribute('tabIndex', '0');
@@ -194,6 +201,9 @@ beestat.component.tile.prototype.decorate_right_ = function(parent) {
const text_container = document.createElement('div');
text_container.innerText = this.get_text_();
text_container.style.fontWeight = beestat.style.font_weight.normal;
+ text_container.style.whiteSpace = 'nowrap';
+ text_container.style.overflow = 'hidden';
+ text_container.style.textOverflow = 'ellipsis';
parent.appendChild(text_container);
}
};
@@ -460,3 +470,18 @@ beestat.component.tile.prototype.set_bubble_color = function(bubble_color) {
}
return this;
};
+
+/**
+ * Set the width of the button.
+ *
+ * @param {number} width
+ *
+ * @return {beestat.component.tile} This.
+ */
+beestat.component.tile.prototype.set_width = function(width) {
+ this.width_ = width;
+ if (this.rendered_ === true) {
+ this.rerender();
+ }
+ return this;
+};
diff --git a/js/component/tile/floor_plan.js b/js/component/tile/floor_plan.js
index 5290c01..b3a3fe8 100644
--- a/js/component/tile/floor_plan.js
+++ b/js/component/tile/floor_plan.js
@@ -7,7 +7,7 @@
beestat.component.tile.floor_plan = function(floor_plan_id) {
this.floor_plan_id_ = floor_plan_id;
- beestat.component.apply(this, arguments);
+ beestat.component.tile.apply(this, arguments);
};
beestat.extend(beestat.component.tile.floor_plan, beestat.component.tile);
diff --git a/js/component/tile/floor_plan_group.js b/js/component/tile/floor_plan_group.js
index 7499d27..15bd757 100644
--- a/js/component/tile/floor_plan_group.js
+++ b/js/component/tile/floor_plan_group.js
@@ -6,7 +6,7 @@
beestat.component.tile.floor_plan_group = function(floor_plan_group) {
this.floor_plan_group_ = floor_plan_group;
- beestat.component.apply(this, arguments);
+ beestat.component.tile.apply(this, arguments);
};
beestat.extend(beestat.component.tile.floor_plan_group, beestat.component.tile);
diff --git a/js/component/tile/thermostat.js b/js/component/tile/thermostat.js
index d3b8193..cc70e85 100644
--- a/js/component/tile/thermostat.js
+++ b/js/component/tile/thermostat.js
@@ -1,12 +1,12 @@
/**
* A tile representing a thermostat.
*
- * @param {integer} thermostat_id
+ * @param {number} thermostat_id
*/
beestat.component.tile.thermostat = function(thermostat_id) {
this.thermostat_id_ = thermostat_id;
- beestat.component.apply(this, arguments);
+ beestat.component.tile.apply(this, arguments);
};
beestat.extend(beestat.component.tile.thermostat, beestat.component.tile);
diff --git a/js/component/tile/thermostat/switcher.js b/js/component/tile/thermostat/switcher.js
new file mode 100644
index 0000000..ed3ec7c
--- /dev/null
+++ b/js/component/tile/thermostat/switcher.js
@@ -0,0 +1,46 @@
+/**
+ * A tile representing a thermostat for the quick switch.
+ *
+ * @param {integer} thermostat_id
+ */
+beestat.component.tile.thermostat.switcher = function(thermostat_id) {
+ this.thermostat_id_ = thermostat_id;
+
+ beestat.component.tile.thermostat.apply(this, arguments);
+};
+beestat.extend(beestat.component.tile.thermostat.switcher, beestat.component.tile.thermostat);
+
+/**
+ * Get the icon for this tile.
+ *
+ * @return {string} The icon.
+ */
+beestat.component.tile.thermostat.switcher.prototype.get_icon_ = function() {
+ return undefined;
+};
+
+/**
+ * Get the text for this tile.
+ *
+ * @return {string} The first line of text.
+ */
+beestat.component.tile.thermostat.switcher.prototype.get_text_ = function() {
+ const thermostat = beestat.cache.thermostat[this.thermostat_id_];
+
+ const temperature = beestat.temperature({
+ 'temperature': thermostat.temperature,
+ 'round': 0,
+ 'units': true
+ });
+
+ return thermostat.name + ' • ' + temperature;
+};
+
+/**
+ * Get the size of this tile.
+ *
+ * @return {string} The size of this tile.
+ */
+beestat.component.tile.thermostat.switcher.prototype.get_size_ = function() {
+ return 'medium';
+};
diff --git a/js/js.php b/js/js.php
index 97c5e34..48ccad2 100755
--- a/js/js.php
+++ b/js/js.php
@@ -50,6 +50,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;
@@ -149,6 +150,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;