diff --git a/css/dashboard.css b/css/dashboard.css index d0877fc..a2d2491 100644 --- a/css/dashboard.css +++ b/css/dashboard.css @@ -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; } diff --git a/js/component/card/floor_plan_editor.js b/js/component/card/floor_plan_editor.js index 313207b..3c88317 100644 --- a/js/component/card/floor_plan_editor.js +++ b/js/component/card/floor_plan_editor.js @@ -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() diff --git a/js/component/card/my_home.js b/js/component/card/my_home.js index b18b61a..ea177f2 100644 --- a/js/component/card/my_home.js +++ b/js/component/card/my_home.js @@ -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)); }; /** diff --git a/js/component/floor_plan.js b/js/component/floor_plan.js index a2c3168..716c102 100644 --- a/js/component/floor_plan.js +++ b/js/component/floor_plan.js @@ -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; diff --git a/js/component/floor_plan_entity/point.js b/js/component/floor_plan_entity/point.js index 259ba25..6f62465 100644 --- a/js/component/floor_plan_entity/point.js +++ b/js/component/floor_plan_entity/point.js @@ -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; +}; diff --git a/js/component/floor_plan_entity/room.js b/js/component/floor_plan_entity/room.js index 89ead7a..6e5f6ed 100644 --- a/js/component/floor_plan_entity/room.js +++ b/js/component/floor_plan_entity/room.js @@ -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, [ diff --git a/js/component/floor_plan_entity/wall.js b/js/component/floor_plan_entity/wall.js index bef694c..868b7d1 100644 --- a/js/component/floor_plan_entity/wall.js +++ b/js/component/floor_plan_entity/wall.js @@ -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; +}; diff --git a/js/component/modal/create_floor_plan.js b/js/component/modal/create_floor_plan.js index 8b33e45..4074a68 100644 --- a/js/component/modal/create_floor_plan.js +++ b/js/component/modal/create_floor_plan.js @@ -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, diff --git a/js/component/modal/delete_floor_plan.js b/js/component/modal/delete_floor_plan.js index 5a673f8..e14932a 100644 --- a/js/component/modal/delete_floor_plan.js +++ b/js/component/modal/delete_floor_plan.js @@ -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); }; /** diff --git a/js/component/modal/update_floor_plan.js b/js/component/modal/update_floor_plan.js new file mode 100644 index 0000000..56559b0 --- /dev/null +++ b/js/component/modal/update_floor_plan.js @@ -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); + } +}; diff --git a/js/component/tile.js b/js/component/tile.js new file mode 100644 index 0000000..3cae5f3 --- /dev/null +++ b/js/component/tile.js @@ -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; +}; diff --git a/js/component/tile/floor_plan.js b/js/component/tile/floor_plan.js new file mode 100644 index 0000000..1ed5486 --- /dev/null +++ b/js/component/tile/floor_plan.js @@ -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'; +}; diff --git a/js/component/tile/floor_plan_group.js b/js/component/tile/floor_plan_group.js new file mode 100644 index 0000000..36c424e --- /dev/null +++ b/js/component/tile/floor_plan_group.js @@ -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'; +}; diff --git a/js/component/tile/thermostat.js b/js/component/tile/thermostat.js new file mode 100644 index 0000000..d3b8193 --- /dev/null +++ b/js/component/tile/thermostat.js @@ -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'; +}; diff --git a/js/component/title.js b/js/component/title.js index 2e194bc..2019430 100644 --- a/js/component/title.js +++ b/js/component/title.js @@ -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_); diff --git a/js/js.php b/js/js.php index c2b13e9..ae9e3cb 100755 --- a/js/js.php +++ b/js/js.php @@ -116,6 +116,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; @@ -126,6 +127,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;