mirror of
				https://github.com/beestat/app.git
				synced 2025-11-03 18:37:01 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			793 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			793 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						||
 * Floor plan editor.
 | 
						||
 *
 | 
						||
 * @param {number} thermostat_id
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor = function(thermostat_id) {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  this.thermostat_id_ = thermostat_id;
 | 
						||
 | 
						||
  // Whether or not to show the editor when loading.
 | 
						||
  this.show_editor_ = beestat.floor_plan.get_bounding_box(
 | 
						||
    beestat.setting('visualize.floor_plan_id')
 | 
						||
  ).x === Infinity;
 | 
						||
 | 
						||
/*  const change_function = beestat.debounce(function() {
 | 
						||
    // todo replace these with (if entity set active false?)
 | 
						||
    delete self.state_.active_group;
 | 
						||
 | 
						||
    self.rerender();
 | 
						||
 | 
						||
    // Center the content if the floor plan changed.
 | 
						||
    if (self.floor_plan_ !== undefined) {
 | 
						||
      self.floor_plan_.center_content();
 | 
						||
    }
 | 
						||
  }, 10);
 | 
						||
 | 
						||
  beestat.dispatcher.addEventListener(
 | 
						||
    'setting.visualize.floor_plan_id',
 | 
						||
    change_function
 | 
						||
  );*/
 | 
						||
 | 
						||
  beestat.component.card.apply(this, arguments);
 | 
						||
 | 
						||
  // Snapping initial
 | 
						||
  if (this.state_.snapping === undefined) {
 | 
						||
    this.state_.snapping = true;
 | 
						||
  }
 | 
						||
 | 
						||
  // The first time this component renders center the content.
 | 
						||
  this.addEventListener('render', function() {
 | 
						||
    if (this.floor_plan_ !== undefined) {
 | 
						||
      self.floor_plan_.center_content();
 | 
						||
      self.removeEventListener('render');
 | 
						||
    }
 | 
						||
  });
 | 
						||
};
 | 
						||
beestat.extend(beestat.component.card.floor_plan_editor, beestat.component.card);
 | 
						||
 | 
						||
/**
 | 
						||
 * Decorate.
 | 
						||
 *
 | 
						||
 * @param {rocket.Elements} parent
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.decorate_contents_ = function(parent) {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  const floor_plan = beestat.cache.floor_plan[beestat.setting('visualize.floor_plan_id')];
 | 
						||
 | 
						||
  // Set group ids if they are not set.
 | 
						||
  floor_plan.data.groups.forEach(function(group) {
 | 
						||
    if (group.group_id === undefined) {
 | 
						||
      group.group_id = window.crypto.randomUUID();
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  /**
 | 
						||
   * If there is an active_group_id, override whatever the current active
 | 
						||
   * group is. Used for undo/redo.
 | 
						||
   */
 | 
						||
  if (this.state_.active_group_id !== undefined) {
 | 
						||
    for (let i = 0; i < floor_plan.data.groups.length; i++) {
 | 
						||
      if (floor_plan.data.groups[i].group_id === this.state_.active_group_id) {
 | 
						||
        this.state_.active_group = floor_plan.data.groups[i];
 | 
						||
        delete this.state_.active_group_id;
 | 
						||
        break;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // If there is no active group, set it to best guess of ground floor.
 | 
						||
  if (this.state_.active_group === undefined) {
 | 
						||
    let closest_distance = Infinity;
 | 
						||
    let closest_group;
 | 
						||
    floor_plan.data.groups.forEach(function(group) {
 | 
						||
      if (Math.abs(group.elevation) < closest_distance) {
 | 
						||
        closest_group = group;
 | 
						||
        closest_distance = Math.abs(group.elevation);
 | 
						||
      }
 | 
						||
    });
 | 
						||
    this.state_.active_group = closest_group;
 | 
						||
  }
 | 
						||
 | 
						||
  this.floor_plan_tile_ = new beestat.component.tile.floor_plan(
 | 
						||
    beestat.setting('visualize.floor_plan_id')
 | 
						||
  )
 | 
						||
    .set_background_color(beestat.style.color.lightblue.base)
 | 
						||
    .set_background_hover_color(beestat.style.color.lightblue.base)
 | 
						||
    .set_text_color('#fff')
 | 
						||
    .set_display('block')
 | 
						||
    .addEventListener('click', function() {
 | 
						||
      self.show_editor_ = !self.show_editor_;
 | 
						||
      self.rerender();
 | 
						||
    })
 | 
						||
    .render(parent);
 | 
						||
 | 
						||
  // Decorate everything.
 | 
						||
  if (this.show_editor_ === true) {
 | 
						||
    const drawing_pane_container = $.createElement('div');
 | 
						||
    drawing_pane_container.style({
 | 
						||
      'margin-top': beestat.style.size.gutter,
 | 
						||
      'position': 'relative',
 | 
						||
      'overflow-x': 'hidden'
 | 
						||
    });
 | 
						||
    parent.appendChild(drawing_pane_container);
 | 
						||
    this.decorate_drawing_pane_(drawing_pane_container);
 | 
						||
 | 
						||
    this.info_pane_container_ = $.createElement('div')
 | 
						||
      .style('margin-top', beestat.style.size.gutter / 2);
 | 
						||
    parent.appendChild(this.info_pane_container_);
 | 
						||
    this.decorate_info_pane_(this.info_pane_container_);
 | 
						||
 | 
						||
    // Help container
 | 
						||
    if (beestat.floor_plan.get_area(beestat.setting('visualize.floor_plan_id')) === 0) {
 | 
						||
      const help_container = document.createElement('div');
 | 
						||
      Object.assign(help_container.style, {
 | 
						||
        'position': 'absolute',
 | 
						||
        'left': '65px',
 | 
						||
        'top': '59px'
 | 
						||
      });
 | 
						||
      drawing_pane_container.appendChild(help_container);
 | 
						||
 | 
						||
      this.helper_tile_ = new beestat.component.tile()
 | 
						||
        .set_text('Start by adding a room')
 | 
						||
        .set_shadow(false)
 | 
						||
        .set_background_color(beestat.style.color.green.base)
 | 
						||
        .set_text_color('#fff')
 | 
						||
        .set_type('pill')
 | 
						||
        .set_size('small')
 | 
						||
        .set_icon('arrow_left')
 | 
						||
        .render($(help_container));
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  const expand_container = document.createElement('div');
 | 
						||
  Object.assign(expand_container.style, {
 | 
						||
    'position': 'absolute',
 | 
						||
    'right': '28px',
 | 
						||
    'top': '70px'
 | 
						||
  });
 | 
						||
  parent.appendChild(expand_container);
 | 
						||
 | 
						||
  new beestat.component.tile()
 | 
						||
    .set_icon(this.show_editor_ === true ? 'chevron_up' : 'chevron_down')
 | 
						||
    .set_size('small')
 | 
						||
    .set_shadow(false)
 | 
						||
    .set_background_hover_color(beestat.style.color.lightblue.base)
 | 
						||
    .set_text_color('#fff')
 | 
						||
    .addEventListener('click', function() {
 | 
						||
      self.show_editor_ = !self.show_editor_;
 | 
						||
      self.rerender();
 | 
						||
    })
 | 
						||
    .render($(expand_container));
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Decorate the drawing pane.
 | 
						||
 *
 | 
						||
 * @param {rocket.Elements} parent
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.decorate_drawing_pane_ = function(parent) {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  // Dispose existing SVG to remove any global listeners.
 | 
						||
  if (this.floor_plan_ !== undefined) {
 | 
						||
    this.floor_plan_.dispose();
 | 
						||
  }
 | 
						||
 | 
						||
  // Create and render a new SVG component.
 | 
						||
  this.floor_plan_ = new beestat.component.floor_plan(
 | 
						||
    beestat.setting('visualize.floor_plan_id'),
 | 
						||
    this.state_
 | 
						||
  );
 | 
						||
 | 
						||
  this.floor_plan_.render(parent);
 | 
						||
 | 
						||
  setTimeout(function() {
 | 
						||
    if (parent.getBoundingClientRect().width > 0) {
 | 
						||
      self.floor_plan_.set_width(parent.getBoundingClientRect().width);
 | 
						||
    }
 | 
						||
  }, 0);
 | 
						||
 | 
						||
  beestat.dispatcher.removeEventListener('resize.floor_plan_editor');
 | 
						||
  beestat.dispatcher.addEventListener('resize.floor_plan_editor', function() {
 | 
						||
    self.floor_plan_.set_width(parent.getBoundingClientRect().width);
 | 
						||
  });
 | 
						||
 | 
						||
  // Rerender when stuff happens
 | 
						||
  this.floor_plan_.addEventListener('add_room', function() {
 | 
						||
    self.update_floor_plan_();
 | 
						||
    self.rerender();
 | 
						||
  });
 | 
						||
  this.floor_plan_.addEventListener('remove_room', function() {
 | 
						||
    self.update_floor_plan_();
 | 
						||
    self.rerender();
 | 
						||
  });
 | 
						||
  this.floor_plan_.addEventListener('remove_point', function() {
 | 
						||
    self.update_floor_plan_();
 | 
						||
    self.rerender();
 | 
						||
  });
 | 
						||
  this.floor_plan_.addEventListener('undo', function() {
 | 
						||
    self.update_floor_plan_();
 | 
						||
    self.rerender();
 | 
						||
  });
 | 
						||
  this.floor_plan_.addEventListener('redo', function() {
 | 
						||
    self.update_floor_plan_();
 | 
						||
    self.rerender();
 | 
						||
  });
 | 
						||
  this.floor_plan_.addEventListener('change_group', self.rerender.bind(this));
 | 
						||
 | 
						||
  const group_below = this.floor_plan_.get_group_below(this.state_.active_group);
 | 
						||
  if (group_below !== undefined) {
 | 
						||
    group_below.rooms.forEach(function(room) {
 | 
						||
      const room_entity = new beestat.component.floor_plan_entity.room(self.floor_plan_, self.state_)
 | 
						||
        .set_enabled(false)
 | 
						||
        .set_room(room)
 | 
						||
        .set_group(self.state_.active_group);
 | 
						||
      room_entity.render(self.floor_plan_.get_g());
 | 
						||
    });
 | 
						||
  }
 | 
						||
 | 
						||
  // Loop over the rooms in this group and add them.
 | 
						||
  let active_room_entity;
 | 
						||
  this.state_.active_group.rooms.forEach(function(room) {
 | 
						||
    const room_entity = new beestat.component.floor_plan_entity.room(self.floor_plan_, self.state_)
 | 
						||
      .set_room(room)
 | 
						||
      .set_group(self.state_.active_group);
 | 
						||
 | 
						||
    // Update the GUI and save when a room changes.
 | 
						||
    room_entity.addEventListener('update', function() {
 | 
						||
      self.floor_plan_.update_infobox();
 | 
						||
      self.update_info_pane_();
 | 
						||
      self.update_floor_plan_tile_();
 | 
						||
      self.update_floor_plan_();
 | 
						||
    });
 | 
						||
 | 
						||
    // Update GUI when a room is selected.
 | 
						||
    room_entity.addEventListener('activate', function() {
 | 
						||
      self.floor_plan_.update_infobox();
 | 
						||
      self.floor_plan_.update_toolbar();
 | 
						||
      self.update_info_pane_();
 | 
						||
      self.update_floor_plan_tile_();
 | 
						||
    });
 | 
						||
 | 
						||
    // Update GUI when a room is deselected.
 | 
						||
    room_entity.addEventListener('inactivate', function() {
 | 
						||
      self.floor_plan_.update_infobox();
 | 
						||
      self.floor_plan_.update_toolbar();
 | 
						||
      self.update_info_pane_();
 | 
						||
      self.update_floor_plan_tile_();
 | 
						||
    });
 | 
						||
 | 
						||
    /**
 | 
						||
     * If there is currently an active room, use it to match to the newly
 | 
						||
     * created room entities and then store it. After this loop is done
 | 
						||
     * activate it to avoid other rooms getting written on top. Also delete
 | 
						||
     * the active room from the state or it will needlessly be inactivated in
 | 
						||
     * the set_active function.
 | 
						||
     */
 | 
						||
    if (
 | 
						||
      self.state_.active_room_entity !== undefined &&
 | 
						||
      room.room_id === self.state_.active_room_entity.get_room().room_id
 | 
						||
    ) {
 | 
						||
      delete self.state_.active_room_entity;
 | 
						||
      active_room_entity = room_entity;
 | 
						||
    }
 | 
						||
 | 
						||
    // Render the room and save to the list of current entities.
 | 
						||
    room_entity.render(self.floor_plan_.get_g());
 | 
						||
  });
 | 
						||
 | 
						||
  if (active_room_entity !== undefined) {
 | 
						||
    active_room_entity.set_active(true);
 | 
						||
  }
 | 
						||
 | 
						||
  /**
 | 
						||
   * If there was an active room, defer to adding it last so it ends up on
 | 
						||
   * top. The set_active function doesn't do anything if the room isn't
 | 
						||
   * rendered otherwise.
 | 
						||
   */
 | 
						||
  if (this.state_.active_room_entity !== undefined) {
 | 
						||
    this.state_.active_room_entity.render(this.floor_plan_.get_g());
 | 
						||
  }
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Decorate the info pane.
 | 
						||
 *
 | 
						||
 * @param {rocket.Elements} parent
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_ = function(parent) {
 | 
						||
  if (this.state_.active_room_entity !== undefined) {
 | 
						||
    this.decorate_info_pane_room_(parent);
 | 
						||
  } else {
 | 
						||
    this.decorate_info_pane_floor_(parent);
 | 
						||
  }
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Decorate the info pane for a floor.
 | 
						||
 *
 | 
						||
 * @param {rocket.Elements} parent
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_floor_ = function(parent) {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  const grid = $.createElement('div')
 | 
						||
    .style({
 | 
						||
      'display': 'grid',
 | 
						||
      'grid-template-columns': 'repeat(auto-fit, minmax(150px, 1fr))',
 | 
						||
      'column-gap': beestat.style.size.gutter
 | 
						||
    });
 | 
						||
  parent.appendChild(grid);
 | 
						||
 | 
						||
  let div;
 | 
						||
 | 
						||
  // Group Name
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const name_input = new beestat.component.input.text()
 | 
						||
    .set_label('Floor Name')
 | 
						||
    .set_placeholder('Unnamed Floor')
 | 
						||
    .set_width('100%')
 | 
						||
    .set_maxlength(50)
 | 
						||
    .set_requirements({
 | 
						||
      'required': true
 | 
						||
    })
 | 
						||
    .render(div);
 | 
						||
 | 
						||
  if (this.state_.active_group.name !== undefined) {
 | 
						||
    name_input.set_value(this.state_.active_group.name);
 | 
						||
  }
 | 
						||
 | 
						||
  name_input.addEventListener('input', function() {
 | 
						||
    self.state_.active_group.name = name_input.get_value();
 | 
						||
    self.floor_plan_.update_infobox();
 | 
						||
  });
 | 
						||
  name_input.addEventListener('change', function() {
 | 
						||
    self.state_.active_group.name = name_input.get_value();
 | 
						||
    self.update_floor_plan_();
 | 
						||
  });
 | 
						||
 | 
						||
  // Elevation
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const elevation_input = new beestat.component.input.text()
 | 
						||
    .set_label('Elevation (' + beestat.setting('units.distance') + ')')
 | 
						||
    .set_placeholder(beestat.distance({
 | 
						||
      'distance': this.state_.active_group.elevation,
 | 
						||
      'round': 2
 | 
						||
    }))
 | 
						||
    .set_value(beestat.distance({
 | 
						||
      'distance': this.state_.active_group.elevation,
 | 
						||
      'round': 2
 | 
						||
    }) || '')
 | 
						||
    .set_width('100%')
 | 
						||
    .set_maxlength(5)
 | 
						||
    .set_requirements({
 | 
						||
      'type': 'decimal',
 | 
						||
      'min_value': beestat.distance(-600),
 | 
						||
      'max_value': beestat.distance(600),
 | 
						||
      'required': true
 | 
						||
    })
 | 
						||
    .set_transform({
 | 
						||
      'type': 'round',
 | 
						||
      'decimals': 2
 | 
						||
    })
 | 
						||
    .render(div);
 | 
						||
 | 
						||
  elevation_input.addEventListener('change', function() {
 | 
						||
    if (elevation_input.meets_requirements() === true) {
 | 
						||
      self.state_.active_group.elevation = beestat.distance({
 | 
						||
        'distance': elevation_input.get_value(),
 | 
						||
        'input_distance_unit': beestat.setting('units.distance'),
 | 
						||
        'output_distance_unit': 'in',
 | 
						||
        'round': 2
 | 
						||
      });
 | 
						||
      self.update_floor_plan_();
 | 
						||
      self.rerender();
 | 
						||
    } else {
 | 
						||
      elevation_input.set_value(beestat.distance(self.state_.active_group.elevation), false);
 | 
						||
      new beestat.component.modal.floor_plan_elevation_help().render();
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  // Ceiling Height
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const height_input = new beestat.component.input.text()
 | 
						||
    .set_label('Ceiling Height (' + beestat.setting('units.distance') + ')')
 | 
						||
    .set_placeholder(beestat.distance({
 | 
						||
      'distance': this.state_.active_group.height,
 | 
						||
      'round': 2
 | 
						||
    }))
 | 
						||
    .set_value(beestat.distance({
 | 
						||
      'distance': this.state_.active_group.height,
 | 
						||
      'round': 2
 | 
						||
    }) || '')
 | 
						||
    .set_width('100%')
 | 
						||
    .set_maxlength(5)
 | 
						||
    .set_requirements({
 | 
						||
      'type': 'decimal',
 | 
						||
      'min_value': beestat.distance(60),
 | 
						||
      'required': true
 | 
						||
    })
 | 
						||
    .set_transform({
 | 
						||
      'type': 'round',
 | 
						||
      'decimals': 2
 | 
						||
    })
 | 
						||
    .render(div);
 | 
						||
 | 
						||
  height_input.addEventListener('change', function() {
 | 
						||
    if (height_input.meets_requirements() === true) {
 | 
						||
      self.state_.active_group.height = beestat.distance({
 | 
						||
        'distance': height_input.get_value(),
 | 
						||
        'input_distance_unit': beestat.setting('units.distance'),
 | 
						||
        'output_distance_unit': 'in',
 | 
						||
        'round': 2
 | 
						||
      });
 | 
						||
      self.update_floor_plan_();
 | 
						||
    } else {
 | 
						||
      height_input.set_value(self.state_.active_group.height, false);
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  // Sensor
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Decorate the info pane for a room.
 | 
						||
 *
 | 
						||
 * @param {rocket.Elements} parent
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.decorate_info_pane_room_ = function(parent) {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  const grid = $.createElement('div')
 | 
						||
    .style({
 | 
						||
      'display': 'grid',
 | 
						||
      'grid-template-columns': 'repeat(auto-fit, minmax(150px, 1fr))',
 | 
						||
      'column-gap': beestat.style.size.gutter
 | 
						||
    });
 | 
						||
  parent.appendChild(grid);
 | 
						||
 | 
						||
  let div;
 | 
						||
 | 
						||
  // Room Name
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const name_input = new beestat.component.input.text()
 | 
						||
    .set_label('Room Name')
 | 
						||
    .set_placeholder('Unnamed Room')
 | 
						||
    .set_width('100%')
 | 
						||
    .set_maxlength(50)
 | 
						||
    .set_requirements({
 | 
						||
      'required': true
 | 
						||
    })
 | 
						||
    .render(div);
 | 
						||
 | 
						||
  if (this.state_.active_room_entity.get_room().name !== undefined) {
 | 
						||
    name_input.set_value(this.state_.active_room_entity.get_room().name);
 | 
						||
  }
 | 
						||
 | 
						||
  name_input.addEventListener('input', function() {
 | 
						||
    self.state_.active_room_entity.get_room().name = name_input.get_value();
 | 
						||
    self.floor_plan_.update_infobox();
 | 
						||
  });
 | 
						||
  name_input.addEventListener('change', function() {
 | 
						||
    self.state_.active_room_entity.get_room().name = name_input.get_value();
 | 
						||
    self.update_floor_plan_();
 | 
						||
  });
 | 
						||
 | 
						||
  // Elevation
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const elevation_input = new beestat.component.input.text()
 | 
						||
    .set_label('Elevation (' + beestat.setting('units.distance') + ')')
 | 
						||
    .set_placeholder(beestat.distance({
 | 
						||
      'distance': this.state_.active_group.elevation,
 | 
						||
      'round': 2
 | 
						||
    }))
 | 
						||
    .set_value(beestat.distance({
 | 
						||
      'distance': this.state_.active_room_entity.get_room().elevation,
 | 
						||
      'round': 2
 | 
						||
    }) || '')
 | 
						||
    .set_width('100%')
 | 
						||
    .set_maxlength(5)
 | 
						||
    .set_requirements({
 | 
						||
      'type': 'decimal',
 | 
						||
      'min_value': beestat.distance(-600),
 | 
						||
      'max_value': beestat.distance(600)
 | 
						||
    })
 | 
						||
    .set_transform({
 | 
						||
      'type': 'round',
 | 
						||
      'decimals': 2
 | 
						||
    })
 | 
						||
    .render(div);
 | 
						||
 | 
						||
  elevation_input.addEventListener('change', function() {
 | 
						||
    if (elevation_input.meets_requirements() === true) {
 | 
						||
      self.state_.active_room_entity.get_room().elevation = beestat.distance({
 | 
						||
        'distance': elevation_input.get_value(),
 | 
						||
        'input_distance_unit': beestat.setting('units.distance'),
 | 
						||
        'output_distance_unit': 'in',
 | 
						||
        'round': 2
 | 
						||
      });
 | 
						||
      self.update_floor_plan_();
 | 
						||
      self.rerender();
 | 
						||
    } else {
 | 
						||
      elevation_input.set_value('', false);
 | 
						||
      new beestat.component.modal.floor_plan_elevation_help().render();
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  // Ceiling Height
 | 
						||
  div = $.createElement('div');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const height_input = new beestat.component.input.text()
 | 
						||
    .set_label('Ceiling Height (' + beestat.setting('units.distance') + ')')
 | 
						||
    .set_placeholder(beestat.distance({
 | 
						||
      'distance': this.state_.active_group.height,
 | 
						||
      'round': 2
 | 
						||
    }))
 | 
						||
    .set_value(beestat.distance({
 | 
						||
      'distance': this.state_.active_room_entity.get_room().height,
 | 
						||
      'round': 2
 | 
						||
    }) || '')
 | 
						||
    .set_width('100%')
 | 
						||
    .set_maxlength(5)
 | 
						||
    .set_requirements({
 | 
						||
      'type': 'decimal',
 | 
						||
      'min_value': beestat.distance(60)
 | 
						||
    })
 | 
						||
    .set_transform({
 | 
						||
      'type': 'round',
 | 
						||
      'decimals': 2
 | 
						||
    })
 | 
						||
    .render(div);
 | 
						||
 | 
						||
  height_input.addEventListener('change', function() {
 | 
						||
    if (height_input.meets_requirements() === true) {
 | 
						||
      self.state_.active_room_entity.get_room().height = beestat.distance({
 | 
						||
        'distance': height_input.get_value(),
 | 
						||
        'input_distance_unit': beestat.setting('units.distance'),
 | 
						||
        'output_distance_unit': 'in',
 | 
						||
        'round': 2
 | 
						||
      });
 | 
						||
      self.update_floor_plan_();
 | 
						||
    } else {
 | 
						||
      height_input.set_value('', false);
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  // Sensor
 | 
						||
  div = $.createElement('div');
 | 
						||
  div.style('position', 'relative');
 | 
						||
  grid.appendChild(div);
 | 
						||
  const sensor_input = new beestat.component.input.select()
 | 
						||
    .add_option({
 | 
						||
      'label': 'None',
 | 
						||
      'value': ''
 | 
						||
    })
 | 
						||
    .set_width('100%')
 | 
						||
    .set_label('Sensor');
 | 
						||
 | 
						||
  const sensors = {};
 | 
						||
  Object.values(beestat.cache.thermostat).forEach(function(thermostat) {
 | 
						||
    const thermostat_sensors = Object.values(beestat.cache.sensor).filter(function(sensor) {
 | 
						||
      return sensor.thermostat_id === thermostat.thermostat_id;
 | 
						||
    })
 | 
						||
      .sort(function(a, b) {
 | 
						||
        return a.name.localeCompare(b.name, 'en', {'sensitivity': 'base'});
 | 
						||
      });
 | 
						||
 | 
						||
    sensors[thermostat.thermostat_id] = thermostat_sensors;
 | 
						||
  });
 | 
						||
 | 
						||
  // Put the sensors in the select.
 | 
						||
  for (let thermostat_id in sensors) {
 | 
						||
    const thermostat = beestat.cache.thermostat[thermostat_id];
 | 
						||
    sensors[thermostat_id].forEach(function(sensor) {
 | 
						||
      sensor_input.add_option({
 | 
						||
        'group': thermostat.name,
 | 
						||
        'value': sensor.sensor_id,
 | 
						||
        'label': sensor.name
 | 
						||
      });
 | 
						||
    });
 | 
						||
  }
 | 
						||
 | 
						||
  sensor_input.render(div);
 | 
						||
 | 
						||
  if (self.state_.active_room_entity.get_room().sensor_id !== undefined) {
 | 
						||
    sensor_input.set_value(self.state_.active_room_entity.get_room().sensor_id);
 | 
						||
  } else {
 | 
						||
    sensor_input.set_value('');
 | 
						||
  }
 | 
						||
 | 
						||
  sensor_input.addEventListener('change', function() {
 | 
						||
    const old_sensor_ids = Object.keys(beestat.floor_plan.get_sensor_ids_map(
 | 
						||
      beestat.setting('visualize.floor_plan_id')
 | 
						||
    ));
 | 
						||
 | 
						||
    if (sensor_input.get_value() === '') {
 | 
						||
      delete self.state_.active_room_entity.get_room().sensor_id;
 | 
						||
    } else {
 | 
						||
      self.state_.active_room_entity.get_room().sensor_id = Number(sensor_input.get_value());
 | 
						||
    }
 | 
						||
 | 
						||
    const new_sensor_ids = Object.keys(beestat.floor_plan.get_sensor_ids_map(
 | 
						||
      beestat.setting('visualize.floor_plan_id')
 | 
						||
    ));
 | 
						||
 | 
						||
    // Delete data if the overall sensor set changes so it's re-fetched.
 | 
						||
    if (old_sensor_ids.sort().join(' ') !== new_sensor_ids.sort().join(' ')) {
 | 
						||
      beestat.cache.delete('data.three_d__runtime_sensor');
 | 
						||
    }
 | 
						||
 | 
						||
    // For the help box
 | 
						||
    self.update_info_pane_();
 | 
						||
 | 
						||
    self.update_floor_plan_();
 | 
						||
  });
 | 
						||
 | 
						||
  // Help container
 | 
						||
  if (
 | 
						||
    Object.keys(beestat.floor_plan.get_sensor_ids_map(beestat.setting('visualize.floor_plan_id'))).length === 0 &&
 | 
						||
    this.state_.active_room_entity !== undefined
 | 
						||
  ) {
 | 
						||
    const help_container = document.createElement('div');
 | 
						||
    Object.assign(help_container.style, {
 | 
						||
      'position': 'absolute',
 | 
						||
      'left': 0,
 | 
						||
      'top': '-9px'
 | 
						||
    });
 | 
						||
    div.appendChild(help_container);
 | 
						||
 | 
						||
    this.helper_tile_ = new beestat.component.tile()
 | 
						||
      .set_text('Assign a sensor')
 | 
						||
      .set_shadow(false)
 | 
						||
      .set_background_color(beestat.style.color.green.base)
 | 
						||
      .set_text_color('#fff')
 | 
						||
      .set_type('pill')
 | 
						||
      .set_size('small')
 | 
						||
      .set_icon('arrow_down')
 | 
						||
      .render($(help_container));
 | 
						||
 | 
						||
    sensor_input.set_label('⠀');
 | 
						||
  }
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Rerender just the info pane to avoid rerendering the entire SVG for
 | 
						||
 * resizes, drags, etc. This isn't super ideal but without making the info
 | 
						||
 * pane a separate component this is the way.
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.update_info_pane_ = function() {
 | 
						||
  var old_parent = this.info_pane_container_;
 | 
						||
  this.info_pane_container_ = $.createElement('div')
 | 
						||
    .style('margin-top', beestat.style.size.gutter / 2);
 | 
						||
 | 
						||
  this.decorate_info_pane_(this.info_pane_container_);
 | 
						||
  old_parent.parentNode().replaceChild(this.info_pane_container_, old_parent);
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Rerender just the top floor pane tile to avoid rerendering the entire SVG
 | 
						||
 * for resizes, drags, etc. This isn't super ideal but without making the info
 | 
						||
 * pane a separate component this is the way.
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.update_floor_plan_tile_ = function() {
 | 
						||
  this.floor_plan_tile_.rerender();
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Get the title of the card.
 | 
						||
 *
 | 
						||
 * @return {string} The title.
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.get_title_ = function() {
 | 
						||
  return 'Floor Plan';
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * 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() {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  // Fake this event since the cache is being directly modified.
 | 
						||
  beestat.dispatcher.dispatchEvent('cache.floor_plan');
 | 
						||
 | 
						||
  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('visualize.floor_plan_id'),
 | 
						||
            'data': self.get_floor_plan_data_(beestat.setting('visualize.floor_plan_id'))
 | 
						||
          }
 | 
						||
        },
 | 
						||
        'update_floor_plan'
 | 
						||
      )
 | 
						||
      .send();
 | 
						||
  }, 1000);
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Get cloned floor plan data.
 | 
						||
 *
 | 
						||
 * @param {number} floor_plan_id Floor plan ID
 | 
						||
 *
 | 
						||
 * @return {object} The modified floor plan data.
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.get_floor_plan_data_ = function(floor_plan_id) {
 | 
						||
  return beestat.clone(beestat.cache.floor_plan[floor_plan_id].data);
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * Decorate the menu.
 | 
						||
 *
 | 
						||
 * @param {rocket.Elements} parent
 | 
						||
 */
 | 
						||
beestat.component.card.floor_plan_editor.prototype.decorate_top_right_ = function(parent) {
 | 
						||
  const self = this;
 | 
						||
 | 
						||
  const menu = (new beestat.component.menu()).render(parent);
 | 
						||
 | 
						||
  if (window.is_demo === false) {
 | 
						||
    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_callback(function() {
 | 
						||
          (new beestat.component.modal.change_floor_plan()).render();
 | 
						||
        }));
 | 
						||
    }
 | 
						||
 | 
						||
    menu.add_menu_item(new beestat.component.menu_item()
 | 
						||
      .set_text('Add New')
 | 
						||
      .set_icon('plus')
 | 
						||
      .set_callback(function() {
 | 
						||
        new beestat.component.modal.create_floor_plan(
 | 
						||
          self.thermostat_id_
 | 
						||
        ).render();
 | 
						||
      }));
 | 
						||
 | 
						||
    if (beestat.setting('visualize.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('visualize.floor_plan_id')
 | 
						||
          ).render();
 | 
						||
        }));
 | 
						||
    }
 | 
						||
 | 
						||
    if (beestat.setting('visualize.floor_plan_id') !== null) {
 | 
						||
      menu.add_menu_item(new beestat.component.menu_item()
 | 
						||
        .set_text('Delete')
 | 
						||
        .set_icon('delete')
 | 
						||
        .set_callback(function() {
 | 
						||
          new beestat.component.modal.delete_floor_plan(
 | 
						||
            beestat.setting('visualize.floor_plan_id')
 | 
						||
          ).render();
 | 
						||
        }));
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  menu.add_menu_item(new beestat.component.menu_item()
 | 
						||
    .set_text('Help')
 | 
						||
    .set_icon('help_circle')
 | 
						||
    .set_callback(function() {
 | 
						||
      window.open('https://doc.beestat.io/86f6e4c44fc84c3cb4e8fb7b16d3d160');
 | 
						||
    }));
 | 
						||
};
 |