1
0
mirror of https://github.com/beestat/app.git synced 2025-05-24 02:14:03 -04:00

More floor plan enhancements and fixes

This commit is contained in:
Jon Ziebell 2022-08-07 18:17:45 -04:00
parent 5678b952a5
commit 0572527d6f
16 changed files with 1079 additions and 197 deletions

View File

@ -415,6 +415,9 @@ input[type=radio] {
.icon.air_filter:before { content: "\F0D43"; }
.icon.air_purifier:before { content: "\F0D44"; }
.icon.alarm_snooze:before { content: "\F068E"; }
.icon.alpha_b:before { content: "\F0AEF"; }
.icon.alpha_b_box:before { content: "\F0B09"; }
.icon.arrow_expand_vertical:before { content: "\F084F"; }
.icon.arrow_left:before { content: "\F004D"; }
.icon.basket_fill:before { content: "\F0077"; }
.icon.basket_unfill:before { content: "\F0078"; }
@ -461,21 +464,40 @@ input[type=radio] {
.icon.home_search:before { content: "\F13B0"; }
.icon.information:before { content: "\F02FC"; }
.icon.key:before { content: "\F0306"; }
.icon.label:before { content: "\F0315"; }
.icon.layers:before { content: "\F0328"; }
.icon.layers_plus:before { content: "\F0E4D"; }
.icon.magnify_close:before { content: "\F0980"; }
.icon.magnify_plus_outline:before { content: "\F06ED"; }
.icon.magnify_minus_outline:before { content: "\F06EC"; }
.icon.magnify_plus_outline:before { content: "\F06ED"; }
.icon.map_marker:before { content: "\F05F8"; }
.icon.menu_down:before { content: "\F035D"; }
.icon.menu_up:before { content: "\F0360"; }
.icon.message:before { content: "\F0361"; }
.icon.network_strength_4:before { content: "\F08FA"; }
.icon.network_strength_off:before { content: "\F08FC"; }
/*.icon.numeric_1_box:before { content: "\F03A4"; }
.icon.numeric_0:before { content: "\F0B39"; }
.icon.numeric_0_box:before { content: "\F03A1"; }
.icon.numeric_10:before { content: "\F0FE9"; }
.icon.numeric_10_box:before { content: "\F0F7D"; }
.icon.numeric_1:before { content: "\F0B3A"; }
.icon.numeric_1_box:before { content: "\F03A4"; }
.icon.numeric_2:before { content: "\F0B3B"; }
.icon.numeric_2_box:before { content: "\F03A7"; }
.icon.numeric_3:before { content: "\F0B3C"; }
.icon.numeric_3_box:before { content: "\F03AA"; }
.icon.numeric_4:before { content: "\F0B3D"; }
.icon.numeric_4_box:before { content: "\F03AD"; }
.icon.numeric_7_box:before { content: "\F03B6"; }*/
.icon.numeric_5:before { content: "\F0B3E"; }
.icon.numeric_5_box:before { content: "\F03B1"; }
.icon.numeric_6:before { content: "\F0B3F"; }
.icon.numeric_6_box:before { content: "\F03B3"; }
.icon.numeric_7:before { content: "\F0B40"; }
.icon.numeric_7_box:before { content: "\F03B6"; }
.icon.numeric_8:before { content: "\F0B41"; }
.icon.numeric_8_box:before { content: "\F03B9"; }
.icon.numeric_9:before { content: "\F0B42"; }
.icon.numeric_9_box:before { content: "\F03BC"; }
.icon.open_in_new:before { content: "\F03CC"; }
.icon.patreon:before { content: "\F0882"; }
.icon.pound:before { content: "\F0423"; }
@ -508,88 +530,9 @@ input[type=radio] {
.icon.wifi_strength_1_alert:before { content: "\F0920"; }
.icon.wifi_strength_4:before { content: "\F0928"; }
.icon.zigbee:before { content: "\F0D41"; }
.icon.home_plus:before { content: "\F0975"; }
.icon.home_switch:before { content: "\F1794"; }
.icon.home_remove:before { content: "\F1247"; }
.icon.arrow_expand_vertical:before { content: "\F084F"; }
.icon.label:before { content: "\F0315"; }
.icon.numeric_0:before { content: "\F0B39"; }
.icon.numeric_0_box:before { content: "\F03A1"; }
.icon.numeric_1:before { content: "\F0B3A"; }
.icon.numeric_1_box:before { content: "\F03A4"; }
.icon.numeric_10:before { content: "\F0FE9"; }
.icon.numeric_10_box:before { content: "\F0F7D"; }
.icon.numeric_2:before { content: "\F0B3B"; }
.icon.numeric_2_box:before { content: "\F03A7"; }
.icon.numeric_3:before { content: "\F0B3C"; }
.icon.numeric_3_box:before { content: "\F03AA"; }
.icon.numeric_4:before { content: "\F0B3D"; }
.icon.numeric_4_box:before { content: "\F03AD"; }
.icon.numeric_5:before { content: "\F0B3E"; }
.icon.numeric_5_box:before { content: "\F03B1"; }
.icon.numeric_6:before { content: "\F0B3F"; }
.icon.numeric_6_box:before { content: "\F03B3"; }
.icon.numeric_7:before { content: "\F0B40"; }
.icon.numeric_7_box:before { content: "\F03B6"; }
.icon.numeric_8:before { content: "\F0B41"; }
.icon.numeric_8_box:before { content: "\F03B9"; }
.icon.numeric_9:before { content: "\F0B42"; }
.icon.numeric_9_box:before { content: "\F03BC"; }
.icon.alpha_a:before { content: "\F0AEE"; }
.icon.alpha_a_box:before { content: "\F0B08"; }
.icon.alpha_b:before { content: "\F0AEF"; }
.icon.alpha_b_box:before { content: "\F0B09"; }
.icon.alpha_c:before { content: "\F0AF0"; }
.icon.alpha_c_box:before { content: "\F0B0A"; }
.icon.alpha_d:before { content: "\F0AF1"; }
.icon.alpha_d_box:before { content: "\F0B0B"; }
.icon.alpha_e:before { content: "\F0AF2"; }
.icon.alpha_e_box:before { content: "\F0B0C"; }
.icon.alpha_f:before { content: "\F0AF3"; }
.icon.alpha_f_box:before { content: "\F0B0D"; }
.icon.alpha_g:before { content: "\F0AF4"; }
.icon.alpha_g_box:before { content: "\F0B0E"; }
.icon.alpha_h:before { content: "\F0AF5"; }
.icon.alpha_h_box:before { content: "\F0B0F"; }
.icon.alpha_i:before { content: "\F0AF6"; }
.icon.alpha_i_box:before { content: "\F0B10"; }
.icon.alpha_j:before { content: "\F0AF7"; }
.icon.alpha_j_box:before { content: "\F0B11"; }
.icon.alpha_k:before { content: "\F0AF8"; }
.icon.alpha_k_box:before { content: "\F0B12"; }
.icon.alpha_l:before { content: "\F0AF9"; }
.icon.alpha_l_box:before { content: "\F0B13"; }
.icon.alpha_m:before { content: "\F0AFA"; }
.icon.alpha_m_box:before { content: "\F0B14"; }
.icon.alpha_n:before { content: "\F0AFB"; }
.icon.alpha_n_box:before { content: "\F0B15"; }
.icon.alpha_o:before { content: "\F0AFC"; }
.icon.alpha_o_box:before { content: "\F0B16"; }
.icon.alpha_p:before { content: "\F0AFD"; }
.icon.alpha_p_box:before { content: "\F0B17"; }
.icon.alpha_q:before { content: "\F0AFE"; }
.icon.alpha_q_box:before { content: "\F0B18"; }
.icon.alpha_r:before { content: "\F0AFF"; }
.icon.alpha_r_box:before { content: "\F0B19"; }
.icon.alpha_s:before { content: "\F0B00"; }
.icon.alpha_s_box:before { content: "\F0B1A"; }
.icon.alpha_t:before { content: "\F0B01"; }
.icon.alpha_t_box:before { content: "\F0B1B"; }
.icon.alpha_u:before { content: "\F0B02"; }
.icon.alpha_u_box:before { content: "\F0B1C"; }
.icon.alpha_v:before { content: "\F0B03"; }
.icon.alpha_v_box:before { content: "\F0B1D"; }
.icon.alpha_w:before { content: "\F0B04"; }
.icon.alpha_w_box:before { content: "\F0B1E"; }
.icon.alpha_x:before { content: "\F0B05"; }
.icon.alpha_x_box:before { content: "\F0B1F"; }
.icon.alpha_y:before { content: "\F0B06"; }
.icon.alpha_y_box:before { content: "\F0B20"; }
.icon.alpha_z:before { content: "\F0B07"; }
.icon.alpha_z_box:before { content: "\F0B21"; }
.icon.pencil:before { content: "\F03EB"; }
.icon.plus:before { content: "\F0415"; }
.icon.delete:before { content: "\F01B4"; }
.icon.f16:before { font-size: 16px; }
.icon.f24:before { font-size: 24px; }

View File

@ -14,7 +14,9 @@ beestat.component.card.floor_plan_editor = function(thermostat_id) {
self.rerender();
// Center the content if the floor plan changed.
self.floor_plan_.center_content();
if (self.floor_plan_ !== undefined) {
self.floor_plan_.center_content();
}
}, 10);
beestat.dispatcher.addEventListener(
@ -33,10 +35,12 @@ beestat.component.card.floor_plan_editor = function(thermostat_id) {
}
// The first time this component renders center the content.
this.addEventListener('render', function() {
self.floor_plan_.center_content();
self.removeEventListener('render');
});
if (self.floor_plan_ !== undefined) {
this.addEventListener('render', function() {
self.floor_plan_.center_content();
self.removeEventListener('render');
});
}
};
beestat.extend(beestat.component.card.floor_plan_editor, beestat.component.card);
@ -52,10 +56,10 @@ beestat.component.card.floor_plan_editor.prototype.decorate_contents_ = function
const center_container = $.createElement('div').style('text-align', 'center');
parent.appendChild(center_container);
center_container.appendChild($.createElement('p').innerText('You haven\'t created any floor plans yet.'));
const get_started_button = new beestat.component.tile()
.set_icon('home_plus')
.set_text('Get Started')
.set_icon('plus')
.set_text('Create my first floor plan')
.set_size('large')
.set_background_color(beestat.style.color.green.dark)
.set_background_hover_color(beestat.style.color.green.light)
.render(center_container)
@ -251,9 +255,9 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
div = $.createElement('div');
grid.appendChild(div);
const elevation_input = new beestat.component.input.text()
.set_label('Elevation (inches)')
.set_placeholder(this.state_.active_group.elevation)
.set_value(this.state_.active_group.elevation || '')
.set_label('Elevation (feet)')
.set_placeholder(this.state_.active_group.elevation / 12)
.set_value(this.state_.active_group.elevation / 12 || '')
.set_width('100%')
.set_maxlength('5')
.set_requirements({
@ -262,12 +266,11 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
})
.render(div);
elevation_input.set_value(this.state_.active_group.elevation);
elevation_input.addEventListener('change', function() {
if (elevation_input.meets_requirements() === true) {
self.state_.active_group.elevation = elevation_input.get_value();
self.state_.active_group.elevation = elevation_input.get_value() * 12;
self.update_floor_plan_();
self.rerender();
} else {
elevation_input.set_value(self.state_.active_group.elevation);
}
@ -277,9 +280,9 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
div = $.createElement('div');
grid.appendChild(div);
const height_input = new beestat.component.input.text()
.set_label('Ceiling Height (inches)')
.set_placeholder(this.state_.active_group.height)
.set_value(this.state_.active_group.height || '')
.set_label('Ceiling Height (feet)')
.set_placeholder(this.state_.active_group.height / 12)
.set_value(this.state_.active_group.height / 12 || '')
.set_width('100%')
.set_maxlength('4')
.set_requirements({
@ -289,11 +292,9 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = f
})
.render(div);
height_input.set_value(this.state_.active_group.height);
height_input.addEventListener('change', function() {
if (height_input.meets_requirements() === true) {
self.state_.active_group.height = height_input.get_value();
self.state_.active_group.height = height_input.get_value() * 12;
self.update_floor_plan_();
} else {
height_input.set_value(self.state_.active_group.height);
@ -353,9 +354,9 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
div = $.createElement('div');
grid.appendChild(div);
const elevation_input = new beestat.component.input.text()
.set_label('Elevation (inches)')
.set_placeholder(this.state_.active_group.elevation)
.set_value(this.state_.active_room.elevation || '')
.set_label('Elevation (feet)')
.set_placeholder(this.state_.active_group.elevation / 12)
.set_value(this.state_.active_room.elevation / 12 || '')
.set_width('100%')
.set_maxlength('5')
.set_requirements({
@ -363,14 +364,11 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
})
.render(div);
if (this.state_.active_room.elevation !== undefined) {
elevation_input.set_value(this.state_.active_room.elevation);
}
elevation_input.addEventListener('change', function() {
if (elevation_input.meets_requirements() === true) {
self.state_.active_room.elevation = elevation_input.get_value();
self.state_.active_room.elevation = elevation_input.get_value() * 12;
self.update_floor_plan_();
self.rerender();
} else {
elevation_input.set_value('');
}
@ -380,9 +378,9 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
div = $.createElement('div');
grid.appendChild(div);
const height_input = new beestat.component.input.text()
.set_label('Ceiling Height (inches)')
.set_placeholder(this.state_.active_group.height)
.set_value(this.state_.active_room.height || '')
.set_label('Ceiling Height (feet)')
.set_placeholder(this.state_.active_group.height / 12)
.set_value(this.state_.active_room.height / 12 || '')
.set_width('100%')
.set_maxlength('4')
.set_requirements({
@ -391,13 +389,9 @@ beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = fu
})
.render(div);
if (this.state_.active_room.height !== undefined) {
height_input.set_value(this.state_.active_room.height);
}
height_input.addEventListener('change', function() {
if (height_input.meets_requirements() === true) {
self.state_.active_room.height = height_input.get_value();
self.state_.active_room.height = height_input.get_value() * 12;
self.update_floor_plan_();
} else {
height_input.set_value('');
@ -491,22 +485,26 @@ beestat.component.card.floor_plan_editor.prototype.get_subtitle_ = function() {
};
/**
* Update the floor plan in the database.
* Update the floor plan in the database. This is throttled so the update can
* only run so fast.
*/
beestat.component.card.floor_plan_editor.prototype.update_floor_plan_ = function() {
new beestat.api()
.add_call(
'floor_plan',
'update',
{
'attributes': {
'floor_plan_id': beestat.setting('floor_plan_id'),
'data': beestat.cache.floor_plan[beestat.setting('floor_plan_id')].data
}
},
'update_floor_plan'
)
.send();
window.clearTimeout(this.update_timeout_);
this.update_timeout_ = window.setTimeout(function() {
new beestat.api()
.add_call(
'floor_plan',
'update',
{
'attributes': {
'floor_plan_id': beestat.setting('floor_plan_id'),
'data': beestat.cache.floor_plan[beestat.setting('floor_plan_id')].data
}
},
'update_floor_plan'
)
.send();
}, 1000);
};
/**
@ -522,7 +520,7 @@ beestat.component.card.floor_plan_editor.prototype.decorate_top_right_ = functio
if (window.is_demo === false) {
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Add New')
.set_icon('home_plus')
.set_icon('plus')
.set_callback(function() {
new beestat.component.modal.create_floor_plan(
self.thermostat_id_
@ -532,22 +530,34 @@ beestat.component.card.floor_plan_editor.prototype.decorate_top_right_ = functio
if (Object.keys(beestat.cache.floor_plan).length > 1) {
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Switch')
.set_icon('home_switch')
.set_icon('swap_horizontal')
.set_callback(function() {
(new beestat.component.modal.change_floor_plan()).render();
}));
}
if (beestat.setting('floor_plan_id') !== null) {
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Edit')
.set_icon('pencil')
.set_callback(function() {
new beestat.component.modal.update_floor_plan(
beestat.setting('floor_plan_id')
).render();
}));
}
if (beestat.setting('floor_plan_id') !== null) {
menu.add_menu_item(new beestat.component.menu_item()
.set_text('Delete')
.set_icon('home_remove')
.set_icon('delete')
.set_callback(function() {
new beestat.component.modal.delete_floor_plan(
beestat.setting('floor_plan_id')
).render();
}));
}
}
menu.add_menu_item(new beestat.component.menu_item()

View File

@ -18,9 +18,20 @@ beestat.component.card.my_home = function(thermostat_id) {
beestat.extend(beestat.component.card.my_home, beestat.component.card);
beestat.component.card.my_home.prototype.decorate_contents_ = function(parent) {
this.decorate_system_type_(parent);
this.decorate_region_(parent);
this.decorate_property_(parent);
const system_container = document.createElement('div');
system_container.style.marginBottom = `${beestat.style.size.gutter}px`;
parent.appendChild(system_container);
this.decorate_system_type_($(system_container));
const region_container = document.createElement('div');
region_container.style.marginBottom = `${beestat.style.size.gutter}px`;
parent.appendChild(region_container);
this.decorate_region_($(region_container));
const property_container = document.createElement('div');
property_container.style.marginBottom = `${beestat.style.size.gutter}px`;
parent.appendChild(property_container);
this.decorate_property_($(property_container));
};
/**

View File

@ -116,6 +116,53 @@ beestat.component.floor_plan.prototype.render = function(parent) {
}
} else if (e.key.toLowerCase() === 's') {
self.toggle_snapping_();
} else if (
e.key.toLowerCase() === 'c' &&
e.ctrlKey === true
) {
self.state_.copied_room = beestat.clone(self.state_.active_room);
} else if (
e.key.toLowerCase() === 'v' &&
e.ctrlKey === true
) {
if (self.state_.copied_room !== undefined) {
self.add_room_(self.state_.copied_room);
}
} else if (
e.key.toLowerCase() === 'z' &&
e.ctrlKey === true
) {
console.log('undo');
} else if (
e.key === 'ArrowLeft' ||
e.key === 'ArrowRight' ||
e.key === 'ArrowUp' ||
e.key === 'ArrowDown'
) {
const entity =
self.state_.active_point_entity ||
self.state_.active_wall_entity ||
self.state_.active_room_entity;
if (entity !== undefined) {
const x = entity.get_x();
const y = entity.get_y();
switch (e.key) {
case 'ArrowLeft':
entity.set_xy(x === null ? null : x - 1, y);
break;
case 'ArrowRight':
entity.set_xy(x === null ? null : x + 1, y);
break;
case 'ArrowUp':
entity.set_xy(x, y === null ? null : y - 1);
break;
case 'ArrowDown':
entity.set_xy(x, y === null ? null : y + 1);
break;
}
}
}
}
};
@ -366,7 +413,9 @@ beestat.component.floor_plan.prototype.update_toolbar = function() {
.set_text_color(beestat.style.color.gray.light)
.set_background_color(beestat.style.color.bluegray.base)
.set_background_hover_color(beestat.style.color.bluegray.light)
.addEventListener('click', this.add_room_.bind(this))
.addEventListener('click', function() {
self.add_room_();
})
);
// Remove room
@ -492,18 +541,32 @@ beestat.component.floor_plan.prototype.update_toolbar = function() {
this.button_group_floors_ = new beestat.component.tile_group();
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
floor_plan.data.groups.forEach(function(group) {
const sorted_groups = Object.values(floor_plan.data.groups)
.sort(function(a, b) {
return a.elevation > b.elevation;
});
let icon_number = 1;
sorted_groups.forEach(function(group) {
const button = new beestat.component.tile()
.set_title(group.name)
.set_text_hover_color(beestat.style.color.lightblue.light)
.set_text_color(beestat.style.color.lightblue.base);
let icon;
if (group.elevation < 0) {
icon = 'alpha_b';
} else {
icon = 'numeric_' + icon_number++;
}
if (group === self.state_.active_group) {
button
.set_icon(group.icon + '_box');
.set_icon(icon + '_box');
} else {
button
.set_icon(group.icon)
.set_icon(icon)
.addEventListener('click', function() {
if (self.state_.active_room_entity !== undefined) {
self.state_.active_room_entity.set_active(false);
@ -557,32 +620,57 @@ beestat.component.floor_plan.prototype.toggle_snapping_ = function() {
/**
* Add a new room.
*
* @param {object} room Optional room to copy from.
*/
beestat.component.floor_plan.prototype.add_room_ = function() {
const new_room_size = 120;
beestat.component.floor_plan.prototype.add_room_ = function(room) {
const svg_view_box = this.view_box_;
const new_room = {
'x': svg_view_box.x + (svg_view_box.width / 2) - (new_room_size / 2),
'y': svg_view_box.y + (svg_view_box.height / 2) - (new_room_size / 2),
'points': [
{
'x': 0,
'y': 0
},
{
'x': new_room_size,
'y': 0
},
{
'x': new_room_size,
'y': new_room_size
},
{
'x': 0,
'y': new_room_size
}
]
};
let new_room;
if (room === undefined) {
const new_room_size = 120;
new_room = {
'x': svg_view_box.x + (svg_view_box.width / 2) - (new_room_size / 2),
'y': svg_view_box.y + (svg_view_box.height / 2) - (new_room_size / 2),
'points': [
{
'x': 0,
'y': 0
},
{
'x': new_room_size,
'y': 0
},
{
'x': new_room_size,
'y': new_room_size
},
{
'x': 0,
'y': new_room_size
}
]
};
} else {
let min_x = Infinity;
let max_x = -Infinity;
let min_y = Infinity;
let max_y = -Infinity;
room.points.forEach(function(point) {
min_x = Math.min(room.x + point.x, min_x);
max_x = Math.max(room.x + point.x, max_x);
min_y = Math.min(room.y + point.y, min_y);
max_y = Math.max(room.y + point.y, max_y);
});
new_room = {
'x': svg_view_box.x + (svg_view_box.width / 2) - ((max_x - min_x) / 2),
'y': svg_view_box.y + (svg_view_box.height / 2) - ((max_y - min_y) / 2),
'points': beestat.clone(room.points)
};
}
this.state_.active_group.rooms.push(new_room);
this.state_.active_room = new_room;

View File

@ -146,6 +146,8 @@ beestat.component.floor_plan_entity.point.prototype.set_xy = function(x, y) {
this.update_rect_();
this.dispatchEvent('update');
return this;
};
@ -243,8 +245,6 @@ beestat.component.floor_plan_entity.point.prototype.after_mousemove_handler_ = f
desired_x,
desired_y
);
this.dispatchEvent('update');
};
/**
@ -358,3 +358,21 @@ beestat.component.floor_plan_entity.point.prototype.set_active = function(active
return this;
};
/**
* Get X
*
* @return {number} x
*/
beestat.component.floor_plan_entity.point.prototype.get_x = function() {
return this.point_.x;
};
/**
* Get Y
*
* @return {number} y
*/
beestat.component.floor_plan_entity.point.prototype.get_y = function() {
return this.point_.y;
};

View File

@ -114,7 +114,7 @@ beestat.component.floor_plan_entity.prototype.decorate_points_ = function(parent
point_entity.addEventListener('update', function() {
self.update_polygon_();
self.update_walls_();
// self.dispatchEvent('update');
self.dispatchEvent('update');
});
// When a point is done moving normalize the points
@ -178,6 +178,7 @@ beestat.component.floor_plan_entity.prototype.decorate_walls_ = function(parent)
self.update_polygon_();
self.update_points_();
self.update_walls_();
self.dispatchEvent('update');
});
// Clear any active points on drag start.
@ -381,6 +382,8 @@ beestat.component.floor_plan_entity.room.prototype.set_xy = function(x, y) {
this.room_.x = Math.round(clamped_x);
this.room_.y = Math.round(clamped_y);
this.dispatchEvent('update');
return beestat.component.floor_plan_entity.prototype.set_xy.apply(
this,
[

View File

@ -326,6 +326,8 @@ beestat.component.floor_plan_entity.wall.prototype.set_xy = function(x, y) {
this.point_2_.y = Math.round(clamped_y - this.room_.get_y());
}
this.dispatchEvent('update');
return this;
};
@ -396,8 +398,6 @@ beestat.component.floor_plan_entity.wall.prototype.after_mousemove_handler_ = fu
desired_y
);
}
this.dispatchEvent('update');
};
/**
@ -577,3 +577,21 @@ beestat.component.floor_plan_entity.wall.prototype.set_active = function(active)
return this;
};
/**
* Get X
*
* @return {number} x
*/
beestat.component.floor_plan_entity.wall.prototype.get_x = function() {
return this.is_vertical_() === true ? this.point_1_.x : null;
};
/**
* Get Y
*
* @return {number} y
*/
beestat.component.floor_plan_entity.wall.prototype.get_y = function() {
return this.is_horizontal_() === true ? this.point_1_.y : null;
};

View File

@ -21,7 +21,7 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
const self = this;
const thermostat = beestat.cache.thermostat[this.thermostat_id_];
parent.appendChild($.createElement('p').innerHTML('Describe your home to help create this floor plan. You can change these values later.'));
parent.appendChild($.createElement('p').innerHTML('Describe your home to help create this floor plan.'));
// Name
(new beestat.component.title('Give your floor plan a name')).render(parent);
@ -46,7 +46,12 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
}
// Floor count
(new beestat.component.title('How many floors does your home have?')).render(parent);
const floor_container = document.createElement('div');
floor_container.style.marginBottom = `${beestat.style.size.gutter}px`;
parent.appendChild(floor_container);
(new beestat.component.title('How many floors does your home have?'))
.render($(floor_container));
const floor_count_input = new beestat.component.input.text()
.set_icon('layers')
@ -57,7 +62,7 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
'type': 'integer',
'required': true
})
.render(parent);
.render($(floor_container));
floor_count_input.addEventListener('change', function() {
self.state_.floor_count = floor_count_input.get_value();
@ -76,7 +81,7 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
// Basement
const basement_checkbox = new beestat.component.input.checkbox()
.set_label('One of these floors is a basement')
.render(parent);
.render($(floor_container));
basement_checkbox.addEventListener('change', function() {
self.state_.basement = basement_checkbox.get_checked();
@ -94,7 +99,6 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
.set_maxlength(2)
.set_requirements({
'min_value': 1,
'max_value': 24,
'type': 'integer',
'required': true
})
@ -108,7 +112,7 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
if (self.state_.height !== undefined) {
height_input.set_value(self.state_.height);
} else if (self.state_.error.height !== true) {
height_input.set_value(9);
height_input.set_value(8);
}
// Address
@ -116,7 +120,7 @@ beestat.component.modal.create_floor_plan.prototype.decorate_contents_ = functio
parent.appendChild($.createElement('p').innerHTML('Addresses are pulled directly from your ecobee data.'));
const radio_group = new beestat.component.radio_group();
const addresses = $.values(beestat.cache.address);
const addresses = Object.values(beestat.cache.address);
addresses.forEach(function(address) {
if (
address.normalized !== null &&
@ -234,7 +238,6 @@ beestat.component.modal.create_floor_plan.prototype.get_buttons_ = function() {
];
for (let i = 0; i < self.state_.floor_count; i++) {
attributes.data.groups.push({
'icon': floor === 0 ? 'alpha_b' : ('numeric_' + floor),
'name': floor === 0 ? 'Basement' : (ordinals[floor - 1] + ' Floor'),
'elevation': elevation,
'height': self.state_.height * 12,

View File

@ -16,21 +16,14 @@ beestat.extend(beestat.component.modal.delete_floor_plan, beestat.component.moda
* @param {rocket.Elements} parent
*/
beestat.component.modal.delete_floor_plan.prototype.decorate_contents_ = function(parent) {
parent.appendChild(
$.createElement('p').innerHTML(
'Are you sure you want to delete this floor plan?'
)
);
const p = document.createElement('p');
p.innerText = 'Are you sure you want to delete this floor plan?';
parent.appendChild(p);
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
floor_plan.data.groups.forEach(function(group) {
parent.appendChild(
$.createElement('div')
.innerHTML(
group.name + ': ' + group.rooms.length + ' room' + (group.rooms.length === 1 ? '' : 's')
)
);
});
new beestat.component.tile.floor_plan(this.floor_plan_id_)
.set_background_color(beestat.style.color.bluegray.base)
.set_text_color('#fff')
.render(parent);
};
/**

View File

@ -0,0 +1,232 @@
/**
* Update a floor plan.
*
* @param {integer} floor_plan_id
*/
beestat.component.modal.update_floor_plan = function(floor_plan_id) {
this.floor_plan_id_ = floor_plan_id;
beestat.component.modal.apply(this, arguments);
this.state_.error = {};
};
beestat.extend(beestat.component.modal.update_floor_plan, beestat.component.modal);
/**
* Decorate
*
* @param {rocket.Elements} parent
*/
beestat.component.modal.update_floor_plan.prototype.decorate_contents_ = function(parent) {
const self = this;
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
parent.appendChild($.createElement('p').innerHTML('Make changes to your floor plan below.'));
// Name
(new beestat.component.title('Give your floor plan a name')).render(parent);
const name_input = new beestat.component.input.text()
.set_icon('label')
.set_maxlength(255)
.set_requirements({
'required': true
})
.render(parent);
name_input.addEventListener('change', function() {
self.state_.name = name_input.get_value();
self.state_.error.name = !name_input.meets_requirements();
});
if (self.state_.name !== undefined) {
name_input.set_value(self.state_.name);
} else if (self.state_.error.name !== true) {
name_input.set_value(floor_plan.name);
}
// Floors
(new beestat.component.title('Floors')).render(parent);
parent.appendChild($.createElement('p').innerHTML('To add or remove floors, create a new floor plan. Change floor settings like name, elevation, and ceiling height on the editor.'));
const grid = document.createElement('div');
Object.assign(grid.style, {
'display': 'grid',
'grid-template-columns': 'repeat(auto-fit, minmax(150px, 1fr))',
'column-gap': `${beestat.style.size.gutter}px`,
'row-gap': `${beestat.style.size.gutter}px`,
'margin-bottom': `${beestat.style.size.gutter}px`
});
parent.appendChild(grid);
const sorted_groups = Object.values(floor_plan.data.groups)
.sort(function(a, b) {
return a.elevation > b.elevation;
});
sorted_groups.forEach(function(group) {
new beestat.component.tile.floor_plan_group(group)
.set_background_color(beestat.style.color.bluegray.base)
.set_text_color('#fff')
.set_display('block')
.render($(grid));
});
// Address
(new beestat.component.title('What is the address for this home?')).render(parent);
parent.appendChild($.createElement('p').innerHTML('Addresses are pulled directly from your ecobee data.'));
const radio_group = new beestat.component.radio_group();
const addresses = Object.values(beestat.cache.address);
addresses.forEach(function(address) {
if (
address.normalized !== null &&
address.normalized.metadata !== undefined &&
address.normalized.metadata.latitude !== undefined &&
address.normalized.metadata.latitude !== null &&
address.normalized.metadata.longitude !== undefined &&
address.normalized.metadata.longitude !== null
) {
const address_parts = [
address.normalized.components.primary_number,
address.normalized.components.street_predirection,
address.normalized.components.street_name,
address.normalized.components.street_suffix,
address.normalized.components.city_name + ',',
address.normalized.components.state_abbreviation,
address.normalized.components.zipcode
];
let radio = new beestat.component.input.radio()
.set_label(address_parts.join(' '))
.set_value(address.address_id);
if (address.address_id === floor_plan.address_id) {
radio.set_checked(true);
self.state_.address_id = Number(address.address_id);
}
radio_group.add_radio(radio);
}
});
radio_group.add_radio(
new beestat.component.input.radio()
.set_label('Not Listed')
.set_checked(floor_plan.address_id === null)
);
radio_group.addEventListener('change', function() {
if (radio_group.get_value() === undefined) {
delete self.state_.address_id;
} else {
self.state_.address_id = Number(radio_group.get_value());
}
});
radio_group.render(parent);
this.decorate_error_(parent);
};
/**
* Get title.
*
* @return {string} The title.
*/
beestat.component.modal.update_floor_plan.prototype.get_title_ = function() {
return 'Edit Floor Plan';
};
/**
* Get the buttons that go on the bottom of this modal.
*
* @return {[beestat.component.button]} The buttons.
*/
beestat.component.modal.update_floor_plan.prototype.get_buttons_ = function() {
const self = this;
const 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_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('Update Floor Plan')
.addEventListener('click', function() {
// Fail if there are errors.
if (
self.state_.error.name === true
) {
self.rerender();
return;
}
const attributes = {
'floor_plan_id': self.floor_plan_id_,
'name': self.state_.name,
'address_id': self.state_.address_id === undefined ? null : self.state_.address_id
};
self.dispose();
new beestat.api()
.add_call(
'floor_plan',
'update',
{
'attributes': attributes
},
'update_floor_plan'
)
.add_call(
'floor_plan',
'read_id',
{},
'floor_plan'
)
.set_callback(function(response) {
beestat.cache.set('floor_plan', response.floor_plan);
})
.send();
});
return [
cancel,
save
];
};
/**
* Decorate the error area.
*
* @param {rocket.Elements} parent
*/
beestat.component.modal.update_floor_plan.prototype.decorate_error_ = function(parent) {
let has_error = false;
var div = $.createElement('div').style({
'background': beestat.style.color.red.base,
'color': '#fff',
'border-radius': beestat.style.size.border_radius,
'padding': beestat.style.size.gutter
});
if (this.state_.error.name === true) {
div.appendChild($.createElement('div').innerText('Name is required.'));
has_error = true;
}
if (has_error === true) {
parent.appendChild(div);
}
};

419
js/component/tile.js Normal file
View File

@ -0,0 +1,419 @@
/**
* A block with an optional icon and up to two lines of text.
*/
beestat.component.tile = function() {
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.tile, beestat.component);
/**
* Decorate
*
* @param {rocket.Elements} parent
*/
beestat.component.tile.prototype.decorate_ = function(parent) {
const self = this;
const background_color = this.background_color_ || 'none';
const text_color = this.text_color_ || '#fff';
const tabbable = this.tabbable_ || false;
const display = this.display_ === 'block' ? 'flex' : 'inline-flex';
let border_radius;
if (this.type_ === 'pill') {
border_radius = (this.get_size_() === 'large' ? 48 : 32);
} else {
border_radius = beestat.style.size.border_radius;
}
this.container_ = document.createElement('div');
Object.assign(this.container_.style, {
'background': background_color,
'border-radius': `${border_radius}px`,
'height': `${(this.get_size_() === 'large' ? 48 : 32)}px`,
'display': display,
'align-items': 'center',
'color': text_color,
'user-select': 'none',
'transition': 'color 200ms ease, background 200ms ease',
'outline-offset': '1px',
'text-align': 'left'
});
parent.appendChild(this.container_);
// Padding. Basically for icon only make it a nice square button.
if (this.get_text_() === undefined) {
Object.assign(this.container_.style, {
'width': `${(this.get_size_() === 'large' ? 48 : 32)}px`,
'justify-content': 'center'
});
} else {
Object.assign(this.container_.style, {
'padding-left': `${(beestat.style.size.gutter / 2)}px`,
'padding-right': `${(beestat.style.size.gutter / 2)}px`,
});
}
// Tabbable
if (tabbable === true) {
this.container_.setAttribute('tabIndex', '0');
}
// Title
if (this.title_ !== undefined) {
this.container_.setAttribute('title', this.title_);
}
// Hover
if (
this.text_hover_color_ !== undefined ||
this.background_hover_color_ !== undefined
) {
this.container_.style.cursor = 'pointer';
const mouseenter_style = {};
if (this.text_hover_color_ !== undefined) {
mouseenter_style.color = this.text_hover_color_;
}
if (this.background_hover_color_ !== undefined) {
mouseenter_style.background = this.background_hover_color_;
}
const mouseleave_style = {};
mouseleave_style.color =
(this.text_color_ !== undefined) ? this.text_color_ : '';
mouseleave_style.background =
(this.background_color_ !== undefined) ? this.background_color_ : '';
this.container_.addEventListener('mouseenter', function() {
Object.assign(self.container_.style, mouseenter_style);
});
this.container_.addEventListener('mouseleave', function() {
Object.assign(self.container_.style, mouseleave_style);
});
}
// Focus
if (tabbable === true) {
this.container_.addEventListener('focus', function() {
self.container_.style.outline = '2px solid #fff';
});
this.container_.addEventListener('blur', function() {
self.container_.style.outline = 'none';
});
this.container_.addEventListener('keydown', function(e) {
if (
e.key === 'Enter' ||
e.key === ' '
) {
self.dispatchEvent(new window.Event('click'));
}
});
}
// Left and right container
const left_container = document.createElement('div');
this.decorate_left_(left_container);
this.container_.appendChild(left_container);
const right_container = document.createElement('div');
this.decorate_right_(right_container);
this.container_.appendChild(right_container);
// Events
this.container_.addEventListener('click', function() {
self.dispatchEvent('click');
});
this.container_.addEventListener('mousedown', function() {
self.dispatchEvent('mousedown');
});
};
beestat.component.tile.prototype.decorate_left_ = function(parent) {
if (this.get_icon_() !== undefined) {
const icon_container = document.createElement('div');
if (this.get_text_() !== undefined) {
icon_container.style.marginRight = (beestat.style.size.gutter / 2) + 'px';
}
parent.appendChild(icon_container);
new beestat.component.icon(this.get_icon_())
.set_bubble_text(this.bubble_text_)
.set_bubble_color(this.bubble_color_)
.set_size(this.get_size_() === 'large' ? 32 : 24)
.render($(icon_container));
}
};
/**
* Decorate the right side of the tile.
*
* @param {HTMLElement} parent
*/
beestat.component.tile.prototype.decorate_right_ = function(parent) {
if (Array.isArray(this.get_text_()) === true) {
const line_1_container = document.createElement('div');
line_1_container.innerText = this.get_text_()[0];
line_1_container.style.fontWeight = beestat.style.font_weight.bold;
parent.appendChild(line_1_container);
const line_2_container = document.createElement('div');
line_2_container.innerText = this.get_text_()[1];
line_2_container.style.fontWeight = beestat.style.font_weight.light;
parent.appendChild(line_2_container);
} else if (this.get_text_() !== undefined) {
const text_container = document.createElement('div');
text_container.innerText = this.get_text_();
text_container.style.fontWeight = beestat.style.font_weight.normal;
parent.appendChild(text_container);
}
};
/**
* Set the icon.
*
* @param {string} icon
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_icon = function(icon) {
this.icon_ = icon;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the size. Default is small.
*
* @param {string} size large|small
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_size = function(size) {
this.size_ = size;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Get the size of this tile.
*
* @return {string} The size of this tile.
*/
beestat.component.tile.prototype.get_size_ = function() {
return this.size_;
};
/**
* Get the icon for this tile.
*
* @return {string} The icon.
*/
beestat.component.tile.prototype.get_icon_ = function() {
return this.icon_;
};
/**
* Set the text of the button.
*
* @param {string|array} text A single string or array of strings. If an array is passed multiple lines of text will be shown.
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_text = function(text) {
this.text_ = text;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Get the text for this tile.
*
* @return {string} The text for this tile.
*/
beestat.component.tile.prototype.get_text_ = function() {
return this.text_;
};
/**
* Set the background color.
*
* @param {string} background_color
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_background_color = function(background_color) {
this.background_color_ = background_color;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the text color.
*
* @param {string} text_color
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_text_color = function(text_color) {
this.text_color_ = text_color;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the background hover color.
*
* @param {string} background_hover_color
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_background_hover_color = function(background_hover_color) {
this.background_hover_color_ = background_hover_color;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the text hover color.
*
* @param {string} text_hover_color
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_text_hover_color = function(text_hover_color) {
this.text_hover_color_ = text_hover_color;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the title for the tile.
*
* @param {string} title
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_title = function(title) {
this.title_ = title;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Get the container for this tile.
*
* @return {array} The container for this tile.
*/
beestat.component.tile.prototype.get_container = function() {
return this.container_;
};
/**
* Set whether or not this is tabbable. Default false.
*
* @param {boolean} tabbable
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_tabbable = function(tabbable) {
this.tabbable_ = tabbable;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set display mode.
*
* @param {string} display inline|block; default inline.
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_display = function(display) {
this.display_ = display;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the type.
*
* @param {string} type Valid value is "pill" for now.
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_type = function(type) {
this.type_ = type;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Do the normal event listener stuff. Only exists for chaining purposes.
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.addEventListener = function() {
rocket.EventTarget.prototype.addEventListener.apply(this, arguments);
return this;
};
/**
* Set the text of the bubble.
*
* @param {string} bubble_text
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_bubble_text = function(bubble_text) {
this.bubble_text_ = bubble_text;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};
/**
* Set the color of the bubble.
*
* @param {string} bubble_color
*
* @return {beestat.component.tile} This.
*/
beestat.component.tile.prototype.set_bubble_color = function(bubble_color) {
this.bubble_color_ = bubble_color;
if (this.rendered_ === true) {
this.rerender();
}
return this;
};

View File

@ -0,0 +1,48 @@
/**
* A tile representing a floor plan.
*
* @param {integer} floor_plan_id
*/
beestat.component.tile.floor_plan = function(floor_plan_id) {
this.floor_plan_id_ = floor_plan_id;
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.tile.floor_plan, beestat.component.tile);
/**
* Get the icon for this tile.
*
* @return {string} The icon.
*/
beestat.component.tile.floor_plan.prototype.get_icon_ = function() {
return 'floor_plan';
};
/**
* Get the text for this tile.
*
* @return {string} The first line of text.
*/
beestat.component.tile.floor_plan.prototype.get_text_ = function() {
const floor_plan = beestat.cache.floor_plan[this.floor_plan_id_];
const line_2_parts = [];
let floor_count = floor_plan.data.groups.length;
line_2_parts.push(floor_count + (floor_count === 1 ? ' Floor' : ' Floors'));
line_2_parts.push(beestat.floor_plan.get_area(this.floor_plan_id_).toLocaleString() + ' sqft');
return [
floor_plan.name,
line_2_parts.join(' • ')
];
};
/**
* Get the size of this tile.
*
* @return {string} The size of this tile.
*/
beestat.component.tile.floor_plan.prototype.get_size_ = function() {
return 'large';
};

View File

@ -0,0 +1,46 @@
/**
* A tile representing a floor plan group.
*
* @param {object} floor_plan_group
*/
beestat.component.tile.floor_plan_group = function(floor_plan_group) {
this.floor_plan_group_ = floor_plan_group;
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.tile.floor_plan_group, beestat.component.tile);
/**
* Get the icon for this tile.
*
* @return {string} The icon.
*/
beestat.component.tile.floor_plan_group.prototype.get_icon_ = function() {
return 'layers';
};
/**
* Get the text for this tile.
*
* @return {string} The first line of text.
*/
beestat.component.tile.floor_plan_group.prototype.get_text_ = function() {
const line_2_parts = [];
let room_count = this.floor_plan_group_.rooms.length;
line_2_parts.push(room_count + (room_count === 1 ? ' Room' : ' Rooms'));
line_2_parts.push(beestat.floor_plan.get_area_group(this.floor_plan_group_).toLocaleString() + ' sqft');
return [
this.floor_plan_group_.name,
line_2_parts.join(' • ')
];
};
/**
* Get the size of this tile.
*
* @return {string} The size of this tile.
*/
beestat.component.tile.floor_plan_group.prototype.get_size_ = function() {
return 'large';
};

View File

@ -0,0 +1,49 @@
/**
* A tile representing a thermostat.
*
* @param {integer} thermostat_id
*/
beestat.component.tile.thermostat = function(thermostat_id) {
this.thermostat_id_ = thermostat_id;
beestat.component.apply(this, arguments);
};
beestat.extend(beestat.component.tile.thermostat, beestat.component.tile);
/**
* Get the icon for this tile.
*
* @return {string} The icon.
*/
beestat.component.tile.thermostat.prototype.get_icon_ = function() {
return 'thermostat';
};
/**
* Get the text for this tile.
*
* @return {string} The first line of text.
*/
beestat.component.tile.thermostat.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.prototype.get_size_ = function() {
return 'large';
};

View File

@ -19,7 +19,6 @@ beestat.component.title.prototype.decorate_ = function(parent) {
.style({
'font-size': beestat.style.font_size.normal,
'font-weight': beestat.style.font_weight.bold,
'margin-top': (beestat.style.size.gutter),
'margin-bottom': (beestat.style.size.gutter / 2)
})
.innerText(this.title_);

View File

@ -116,6 +116,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/modal/newsletter.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/air_quality_detail_custom.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/create_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/update_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/delete_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/modal/change_floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/input.js"></script>' . PHP_EOL;
@ -126,6 +127,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
echo '<script src="/js/component/tile_group.js"></script>' . PHP_EOL;
echo '<script src="/js/component/tile.js"></script>' . PHP_EOL;
echo '<script src="/js/component/tile/floor_plan.js"></script>' . PHP_EOL;
echo '<script src="/js/component/tile/floor_plan_group.js"></script>' . PHP_EOL;
echo '<script src="/js/component/tile/thermostat.js"></script>' . PHP_EOL;
echo '<script src="/js/component/title.js"></script>' . PHP_EOL;
echo '<script src="/js/component/floor_plan_entity.js"></script>' . PHP_EOL;