mirror of
				https://github.com/beestat/app.git
				synced 2025-11-04 02:47:01 -05:00 
			
		
		
		
	Added settings page and all the things that go along with it.
This commit is contained in:
		
							parent
							
								
									65cfc07220
								
							
						
					
					
						commit
						06073bfc25
					
				@ -57,6 +57,28 @@ class api_cache extends crud {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Clear the cache for a specific API call.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $api_call The API call to clear the cache for.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed The updated cache row or null if it wasn't cached.
 | 
			
		||||
   */
 | 
			
		||||
  public function clear_cache($api_call) {
 | 
			
		||||
    $key = $this->generate_key($api_call);
 | 
			
		||||
    $cache_hits = $this->read(['key' => $key]);
 | 
			
		||||
 | 
			
		||||
    if(count($cache_hits) > 0) {
 | 
			
		||||
      $cache_hit = $cache_hits[0];
 | 
			
		||||
      $attributes = [];
 | 
			
		||||
      $attributes['expires_at'] = date('Y-m-d H:i:s', strtotime('1970-01-01 00:00:01'));
 | 
			
		||||
      $attributes['api_cache_id'] = $cache_hit['api_cache_id'];
 | 
			
		||||
      return $this->update($attributes);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Retrieve a cache entry with a matching key that is not expired.
 | 
			
		||||
   *
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,27 @@ final class api_call {
 | 
			
		||||
   */
 | 
			
		||||
  private $alias;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Bypass the cache read.
 | 
			
		||||
   *
 | 
			
		||||
   * @var boolean
 | 
			
		||||
   */
 | 
			
		||||
  private $bypass_cache_read;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Bypass the cache write.
 | 
			
		||||
   *
 | 
			
		||||
   * @var boolean
 | 
			
		||||
   */
 | 
			
		||||
  private $bypass_cache_write;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Clear the cache for this API call, don't actually run the call.
 | 
			
		||||
   *
 | 
			
		||||
   * @var boolean
 | 
			
		||||
   */
 | 
			
		||||
  private $clear_cache;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The current auto-alias. If an alias is not provided, an auto-alias is
 | 
			
		||||
   * assigned.
 | 
			
		||||
@ -87,6 +108,29 @@ final class api_call {
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->alias = $this->get_auto_alias();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Note the following three parameters will come in as strings when not in
 | 
			
		||||
     * a batch and boolean values when in a batch because of the JSON. Cast to
 | 
			
		||||
     * boolean to support various representations.
 | 
			
		||||
     */
 | 
			
		||||
    if(isset($api_call['bypass_cache_read']) === true) {
 | 
			
		||||
      $this->bypass_cache_read = ((bool) $api_call['bypass_cache_read'] === true);
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->bypass_cache_read = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(isset($api_call['bypass_cache_write']) === true) {
 | 
			
		||||
      $this->bypass_cache_write = ((bool) $api_call['bypass_cache_write'] === true);
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->bypass_cache_write = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(isset($api_call['clear_cache']) === true) {
 | 
			
		||||
      $this->clear_cache = ((bool) $api_call['clear_cache'] === true);
 | 
			
		||||
    } else {
 | 
			
		||||
      $this->clear_cache = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@ -103,15 +147,18 @@ final class api_call {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Caching! If this API call is configured for caching,
 | 
			
		||||
    // $cache_config = $this->setting->get('cache');
 | 
			
		||||
    if( // Is cacheable
 | 
			
		||||
      isset($this->resource::$cache) === true &&
 | 
			
		||||
      isset($this->resource::$cache[$this->method]) === true
 | 
			
		||||
    ) {
 | 
			
		||||
      $api_cache_instance = new api_cache();
 | 
			
		||||
      if($this->clear_cache === true) {
 | 
			
		||||
        $this->response = $api_cache_instance->clear_cache($this);
 | 
			
		||||
        $this->cached_until = date('Y-m-d H:i:s', strtotime('1970-01-01 00:00:01'));
 | 
			
		||||
      } else {
 | 
			
		||||
        $api_cache = $api_cache_instance->retrieve($this);
 | 
			
		||||
 | 
			
		||||
      if($api_cache !== null) {
 | 
			
		||||
        if($api_cache !== null && $this->bypass_cache_read === false) {
 | 
			
		||||
          // If there was a cache entry available, use that.
 | 
			
		||||
          $this->response = $api_cache['response_data'];
 | 
			
		||||
          $this->cached_until = date('Y-m-d H:i:s', strtotime($api_cache['expires_at']));
 | 
			
		||||
@ -122,6 +169,7 @@ final class api_call {
 | 
			
		||||
            $this->arguments
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          if($this->bypass_cache_write === false) {
 | 
			
		||||
            $api_cache = $api_cache_instance->cache(
 | 
			
		||||
              $this,
 | 
			
		||||
              $this->response,
 | 
			
		||||
@ -130,6 +178,8 @@ final class api_call {
 | 
			
		||||
            $this->cached_until = date('Y-m-d H:i:s', strtotime($api_cache['expires_at']));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    else { // Not cacheable
 | 
			
		||||
      $this->response = call_user_func_array(
 | 
			
		||||
        [$resource_instance, $this->method],
 | 
			
		||||
 | 
			
		||||
@ -114,14 +114,18 @@ class profile extends cora\api {
 | 
			
		||||
     */
 | 
			
		||||
    $max_lookahead = 1800; // 30 min
 | 
			
		||||
 | 
			
		||||
    // Get some stuff
 | 
			
		||||
    $thermostat = $this->api('thermostat', 'get', $thermostat_id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempt to ignore the effects of solar heating by only looking at
 | 
			
		||||
     * samples when the sun is down.
 | 
			
		||||
     */
 | 
			
		||||
    $ignore_solar_heating = false;
 | 
			
		||||
 | 
			
		||||
    // Get some stuff
 | 
			
		||||
    $thermostat = $this->api('thermostat', 'get', $thermostat_id);
 | 
			
		||||
    $ignore_solar_gain = $this->api(
 | 
			
		||||
      'user',
 | 
			
		||||
      'get_setting',
 | 
			
		||||
      'thermostat.' . $thermostat_id . '.profile.ignore_solar_gain'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if($thermostat['system_type']['reported']['heat']['equipment'] !== null) {
 | 
			
		||||
      $system_type_heat = $thermostat['system_type']['reported']['heat']['equipment'];
 | 
			
		||||
@ -165,6 +169,26 @@ class profile extends cora\api {
 | 
			
		||||
      ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Get latitude/longitude. If that's not possible, disable solar gain
 | 
			
		||||
    // check.
 | 
			
		||||
    if($ignore_solar_gain === true) {
 | 
			
		||||
      if($thermostat['address_id'] === null) {
 | 
			
		||||
        $ignore_solar_gain = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        $address = $this->api('address', 'get', $thermostat['address_id']);
 | 
			
		||||
        if(
 | 
			
		||||
          isset($address['normalized']['metadata']) === false ||
 | 
			
		||||
          isset($address['normalized']['metadata']['latitude']) === false ||
 | 
			
		||||
          isset($address['normalized']['metadata']['longitude']) === false
 | 
			
		||||
        ) {
 | 
			
		||||
          $ignore_solar_gain = false;
 | 
			
		||||
        } else {
 | 
			
		||||
          $latitude = $address['normalized']['metadata']['latitude'];
 | 
			
		||||
          $longitude = $address['normalized']['metadata']['longitude'];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get all of the relevant data
 | 
			
		||||
    $thermostat_ids = [];
 | 
			
		||||
    foreach($group_thermostats as $group_thermostat) {
 | 
			
		||||
@ -254,7 +278,6 @@ class profile extends cora\api {
 | 
			
		||||
        $degree_days_temperatures = [];
 | 
			
		||||
        while($row = $result->fetch_assoc()) {
 | 
			
		||||
          $timestamp = strtotime($row['timestamp']);
 | 
			
		||||
          $hour = date('G', $timestamp);
 | 
			
		||||
          $date = date('Y-m-d', $timestamp);
 | 
			
		||||
 | 
			
		||||
          // Degree days
 | 
			
		||||
@ -308,13 +331,16 @@ class profile extends cora\api {
 | 
			
		||||
          $runtime_seconds['cool_1'] += $row['cool_1'];
 | 
			
		||||
          $runtime_seconds['cool_2'] += $row['cool_2'];
 | 
			
		||||
 | 
			
		||||
          if (
 | 
			
		||||
            $ignore_solar_heating === true &&
 | 
			
		||||
            $hour > 6 &&
 | 
			
		||||
            $hour < 22
 | 
			
		||||
          // Ignore data between sunrise and sunset.
 | 
			
		||||
          if($ignore_solar_gain === true) {
 | 
			
		||||
            $sun_info = date_sun_info($timestamp, $latitude, $longitude);
 | 
			
		||||
            if(
 | 
			
		||||
              $timestamp > $sun_info['sunrise'] &&
 | 
			
		||||
              $timestamp < $sun_info['sunset']
 | 
			
		||||
            ) {
 | 
			
		||||
              continue;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (isset($runtime[$timestamp]) === false) {
 | 
			
		||||
            $runtime[$timestamp] = [];
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,8 @@ class thermostat extends cora\crud {
 | 
			
		||||
      'set_reported_system_types',
 | 
			
		||||
      'generate_profile',
 | 
			
		||||
      'generate_profiles',
 | 
			
		||||
      'get_metrics'
 | 
			
		||||
      'get_metrics',
 | 
			
		||||
      'update'
 | 
			
		||||
    ],
 | 
			
		||||
    'public' => []
 | 
			
		||||
  ];
 | 
			
		||||
@ -240,7 +241,8 @@ class thermostat extends cora\crud {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Generate a new profile for this thermostat.
 | 
			
		||||
   * Generate a new profile for this thermostat. This is called from the GUI
 | 
			
		||||
   * often but is cached.
 | 
			
		||||
   *
 | 
			
		||||
   * @param int $thermostat_id
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										69
									
								
								api/user.php
									
									
									
									
									
								
							
							
						
						
									
										69
									
								
								api/user.php
									
									
									
									
									
								
							@ -166,8 +166,9 @@ class user extends cora\crud {
 | 
			
		||||
      $settings = $user['settings'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $settings[$key] = $value;
 | 
			
		||||
    $settings = $this->update_setting_($settings, $key, $value);
 | 
			
		||||
 | 
			
		||||
    // Disallow setting changes in the demo.
 | 
			
		||||
    if($this->setting->is_demo() === false) {
 | 
			
		||||
      $this->update(
 | 
			
		||||
        [
 | 
			
		||||
@ -180,6 +181,72 @@ class user extends cora\crud {
 | 
			
		||||
    return $settings;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Recursively update the setting array.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $settings Settings array
 | 
			
		||||
   * @param string $key Key to update. Dots indicate a path.
 | 
			
		||||
   * @param mixed $value Value to set.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array Updated settings array.
 | 
			
		||||
   */
 | 
			
		||||
  private function update_setting_($settings, $key, $value) {
 | 
			
		||||
    $path = explode('.', $key);
 | 
			
		||||
    if(count($path) > 1) {
 | 
			
		||||
      $this_key = array_shift($path);
 | 
			
		||||
      if(isset($settings[$this_key]) === false) {
 | 
			
		||||
        $settings[$this_key] = [];
 | 
			
		||||
      }
 | 
			
		||||
      $settings[$this_key] = $this->update_setting_(
 | 
			
		||||
        $settings[$this_key],
 | 
			
		||||
        implode('.', $path),
 | 
			
		||||
        $value
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      $settings[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return $settings;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a specific setting.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $key The setting to get. Supports dotted paths.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed The setting. Null if not set.
 | 
			
		||||
   */
 | 
			
		||||
  public function get_setting($key) {
 | 
			
		||||
    $user = $this->get($this->session->get_user_id());
 | 
			
		||||
    return $this->get_setting_($user['settings'], $key);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Recursive helper function for getting a setting.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $settings Settings array
 | 
			
		||||
   * @param string $key The key of the setting to get.
 | 
			
		||||
   *
 | 
			
		||||
   * @return mixed The setting. Null if not set.
 | 
			
		||||
   */
 | 
			
		||||
  private function get_setting_($settings, $key) {
 | 
			
		||||
    $path = explode('.', $key);
 | 
			
		||||
    if(count($path) > 1) {
 | 
			
		||||
      $this_key = array_shift($path);
 | 
			
		||||
      if(isset($settings[$this_key]) === true) {
 | 
			
		||||
        return $this->get_setting_($settings[$this_key], implode('.', $path));
 | 
			
		||||
      } else {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      if(isset($settings[$key]) === true) {
 | 
			
		||||
        return $settings[$key];
 | 
			
		||||
      } else {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Set a sync_status on a user to the current datetime.
 | 
			
		||||
   *
 | 
			
		||||
 | 
			
		||||
@ -218,6 +218,56 @@ a.inverted:active {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Inputs
 | 
			
		||||
 */
 | 
			
		||||
input[type=checkbox] {
 | 
			
		||||
  visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  user-select: none;
 | 
			
		||||
  margin-bottom: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox label {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: 20px;
 | 
			
		||||
  height: 20px;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  box-shadow: inset 0px 1px 1px rgba(0,0,0,0.5), 0px 1px 0px rgba(255, 255, 255, 0.4);
 | 
			
		||||
  background: #37474f; /* bluegray light */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox label::after {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  content: '';
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: 12px;
 | 
			
		||||
  height: 8px;
 | 
			
		||||
  background: transparent;
 | 
			
		||||
  top: 5px;
 | 
			
		||||
  left: 4px;
 | 
			
		||||
  border: 3px solid #20bf6b;
 | 
			
		||||
  border-top: none;
 | 
			
		||||
  border-right: none;
 | 
			
		||||
  transform: rotate(-45deg);
 | 
			
		||||
  transition: opacity 200ms ease;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox input[type=checkbox]:checked + label:after {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.checkbox input[type=checkbox]:disabled + label:after {
 | 
			
		||||
  opacity: 0.25;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is a stripped down version of https://flexgridlite.elliotdahl.com/
 | 
			
		||||
@ -344,6 +394,7 @@ a.inverted:active {
 | 
			
		||||
.icon.close_network:before { content: "\F015B"; }
 | 
			
		||||
.icon.cloud_question:before { content: "\F0A39"; }
 | 
			
		||||
.icon.code_tags:before { content: "\F0174"; }
 | 
			
		||||
.icon.cog:before { content: "\F0493"; }
 | 
			
		||||
.icon.dots_vertical:before { content: "\F01D9"; }
 | 
			
		||||
.icon.download:before { content: "\F01DA"; }
 | 
			
		||||
.icon.earth:before { content: "\F01E7"; }
 | 
			
		||||
 | 
			
		||||
@ -114,18 +114,30 @@ beestat.api.prototype.send = function(opt_api_call) {
 | 
			
		||||
 * @param {string} method The method.
 | 
			
		||||
 * @param {Object=} opt_args Optional arguments.
 | 
			
		||||
 * @param {string=} opt_alias Optional alias (required for batch API calls).
 | 
			
		||||
 * @param {boolean=} opt_bypass_cache_read Optional bypass of cache read.
 | 
			
		||||
 * @param {boolean=} opt_bypass_cache_write Optional bypass of cache write.
 | 
			
		||||
 *
 | 
			
		||||
 * @return {beestat.api} This.
 | 
			
		||||
 */
 | 
			
		||||
beestat.api.prototype.add_call = function(resource, method, opt_args, opt_alias) {
 | 
			
		||||
beestat.api.prototype.add_call = function(resource, method, opt_args, opt_alias, opt_bypass_cache_read, opt_bypass_cache_write, opt_clear_cache) {
 | 
			
		||||
  var api_call = {
 | 
			
		||||
    'resource': resource,
 | 
			
		||||
    'method': method,
 | 
			
		||||
    'arguments': JSON.stringify(beestat.default_value(opt_args, {}))
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (opt_alias !== undefined) {
 | 
			
		||||
    api_call.alias = opt_alias;
 | 
			
		||||
  }
 | 
			
		||||
  if (opt_bypass_cache_read !== undefined) {
 | 
			
		||||
    api_call.bypass_cache_read = opt_bypass_cache_read;
 | 
			
		||||
  }
 | 
			
		||||
  if (opt_bypass_cache_write !== undefined) {
 | 
			
		||||
    api_call.bypass_cache_write = opt_bypass_cache_write;
 | 
			
		||||
  }
 | 
			
		||||
  if (opt_clear_cache !== undefined) {
 | 
			
		||||
    api_call.clear_cache = opt_clear_cache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this.api_calls_.push(api_call);
 | 
			
		||||
 | 
			
		||||
@ -296,7 +308,9 @@ beestat.api.prototype.get_cached_ = function(api_call) {
 | 
			
		||||
  var cached = beestat.api.cache[this.get_key_(api_call)];
 | 
			
		||||
  if (
 | 
			
		||||
    cached !== undefined &&
 | 
			
		||||
    moment().isAfter(cached.until) === false
 | 
			
		||||
    moment().isAfter(cached.until) === false &&
 | 
			
		||||
    api_call.bypass_cache_read !== true &&
 | 
			
		||||
    api_call.clear_cache !== true
 | 
			
		||||
  ) {
 | 
			
		||||
    return cached;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,30 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Get or set a setting.
 | 
			
		||||
 * Get or set a setting. ESLint Forgive my variable naming sins for the sake
 | 
			
		||||
 * of no-shadow.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {mixed} key If a string, get/set that specific key. If an object, set all the specified keys in the object.
 | 
			
		||||
 * @param {mixed} argument_1 If a string, get/set that specific key. If an
 | 
			
		||||
 * object, set all the specified keys in the object.
 | 
			
		||||
 * @param {mixed} opt_value If a string, set the specified key to this value.
 | 
			
		||||
 * @param {mixed} opt_callback Optional callback.
 | 
			
		||||
 *
 | 
			
		||||
 * @return {mixed} The setting if requesting (undefined if not set), undefined
 | 
			
		||||
 * otherwise.
 | 
			
		||||
 */
 | 
			
		||||
beestat.setting = function(key, opt_value, opt_callback) {
 | 
			
		||||
  var user = beestat.user.get();
 | 
			
		||||
beestat.setting = function(argument_1, opt_value, opt_callback) {
 | 
			
		||||
  const user = beestat.user.get();
 | 
			
		||||
  if (user.settings === null) {
 | 
			
		||||
    user.settings = {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var defaults = {
 | 
			
		||||
  // TODO Some of these are still strings instead of ints in the database.
 | 
			
		||||
  if (user.settings.thermostat_id !== undefined) {
 | 
			
		||||
    user.settings.thermostat_id = parseInt(
 | 
			
		||||
      user.settings.thermostat_id,
 | 
			
		||||
      10
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const defaults = {
 | 
			
		||||
    'runtime_thermostat_detail_range_type': 'dynamic',
 | 
			
		||||
    'runtime_thermostat_detail_range_static_begin': moment()
 | 
			
		||||
      .subtract(3, 'day')
 | 
			
		||||
@ -37,49 +50,113 @@ beestat.setting = function(key, opt_value, opt_callback) {
 | 
			
		||||
 | 
			
		||||
    'temperature_unit': '°F',
 | 
			
		||||
 | 
			
		||||
    'first_run': true
 | 
			
		||||
    'first_run': true,
 | 
			
		||||
 | 
			
		||||
    'thermostat.#.profile.ignore_solar_gain': false
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  if (user.settings === null) {
 | 
			
		||||
    user.settings = {};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * TODO This is temporary until I get all the setting data types under
 | 
			
		||||
   * control. Just doing this so other parts of the application can be built out
 | 
			
		||||
   * properly.
 | 
			
		||||
   */
 | 
			
		||||
  if (user.settings.thermostat_id !== undefined) {
 | 
			
		||||
    user.settings.thermostat_id = parseInt(
 | 
			
		||||
      user.settings.thermostat_id,
 | 
			
		||||
      10
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (opt_value === undefined && typeof key !== 'object') {
 | 
			
		||||
    if (user.settings[key] !== undefined) {
 | 
			
		||||
      return user.settings[key];
 | 
			
		||||
    } else if (defaults[key] !== undefined) {
 | 
			
		||||
      return defaults[key];
 | 
			
		||||
    }
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
  var settings;
 | 
			
		||||
  if (typeof key === 'object') {
 | 
			
		||||
    settings = key;
 | 
			
		||||
  // Figure out what we're trying to do.
 | 
			
		||||
  let settings;
 | 
			
		||||
  let key;
 | 
			
		||||
  let mode;
 | 
			
		||||
  if (typeof argument_1 === 'object') {
 | 
			
		||||
    settings = argument_1;
 | 
			
		||||
  } else {
 | 
			
		||||
    key = argument_1;
 | 
			
		||||
 | 
			
		||||
    if (opt_value !== undefined) {
 | 
			
		||||
      settings = {};
 | 
			
		||||
      settings[key] = opt_value;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  mode = (settings !== undefined || opt_value !== undefined) ? 'set' : 'get';
 | 
			
		||||
 | 
			
		||||
  var api = new beestat.api();
 | 
			
		||||
  // Get the requested value.
 | 
			
		||||
  if (mode === 'get') {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a value nested in an object from a string path.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {object} o The object to search in.
 | 
			
		||||
     * @param {string} p) The path (ex: thermostat.1.profile.ignore_solar_gain)
 | 
			
		||||
     *
 | 
			
		||||
     * @throws {exception} If the path is invalid.
 | 
			
		||||
     * @return {mixed} The value, or undefined if it doesn't exist.
 | 
			
		||||
     */
 | 
			
		||||
    const get_value_from_path = (o, p) => p.split('.').reduce((a, v) => a[v], o);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the default value of a setting.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} k The setting to get.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {mixed} The default value, or undefined if there is none.
 | 
			
		||||
     */
 | 
			
		||||
    const get_default_value = function(k) {
 | 
			
		||||
      // Replace any numeric key parts with a # as a placeholder.
 | 
			
		||||
      let old_parts = k.split('.');
 | 
			
		||||
      let new_parts = [];
 | 
			
		||||
      old_parts.forEach(function(part) {
 | 
			
		||||
        if (isNaN(part) === false) {
 | 
			
		||||
          new_parts.push('#');
 | 
			
		||||
        } else {
 | 
			
		||||
          new_parts.push(part);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return defaults[new_parts.join('.')];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let value;
 | 
			
		||||
    try {
 | 
			
		||||
      value = get_value_from_path(user.settings, key);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      value = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (value === undefined ? get_default_value(key) : value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Set the requested value.
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Recursively update the setting object.
 | 
			
		||||
   *
 | 
			
		||||
   * @param {object} user_settings Settings object
 | 
			
		||||
   * @param {string} k Key to update. Dots indicate a path.
 | 
			
		||||
   * @param {mixed} v Value to set
 | 
			
		||||
   *
 | 
			
		||||
   * @return {object} Updated settings object.
 | 
			
		||||
   */
 | 
			
		||||
  const update_setting = function(user_settings, k, v) {
 | 
			
		||||
    let path = k.split('.');
 | 
			
		||||
    if (path.length > 1) {
 | 
			
		||||
      const this_key = path.shift();
 | 
			
		||||
      if (user_settings[this_key] === undefined) {
 | 
			
		||||
        user_settings[this_key] = {};
 | 
			
		||||
      }
 | 
			
		||||
      if (typeof user_settings[this_key] !== 'object') {
 | 
			
		||||
        throw new Error('Tried to set sub-key of non-object setting.');
 | 
			
		||||
      }
 | 
			
		||||
      user_settings[this_key] = update_setting(
 | 
			
		||||
        user_settings[this_key],
 | 
			
		||||
        path.join('.'),
 | 
			
		||||
        v
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      user_settings[k] = v;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return user_settings;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const api = new beestat.api();
 | 
			
		||||
  api.set_callback(opt_callback);
 | 
			
		||||
 | 
			
		||||
  var has_calls = false;
 | 
			
		||||
  let has_calls = false;
 | 
			
		||||
 | 
			
		||||
  for (var k in settings) {
 | 
			
		||||
    if (user.settings[k] !== settings[k]) {
 | 
			
		||||
      user.settings[k] = settings[k];
 | 
			
		||||
  for (let k in settings) {
 | 
			
		||||
    if (beestat.setting(k) !== settings[k]) {
 | 
			
		||||
      user.settings = update_setting(user.settings, k, settings[k]);
 | 
			
		||||
 | 
			
		||||
      beestat.dispatcher.dispatchEvent('setting.' + k);
 | 
			
		||||
 | 
			
		||||
@ -107,4 +184,6 @@ beestat.setting = function(key, opt_value, opt_callback) {
 | 
			
		||||
      opt_callback();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return undefined;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -580,38 +580,6 @@ beestat.component.card.runtime_thermostat_summary.prototype.decorate_top_right_
 | 
			
		||||
      .set_callback(function() {
 | 
			
		||||
        self.chart_.reset_zoom();
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
    if (beestat.setting('runtime_thermostat_summary_gap_fill') === true) {
 | 
			
		||||
      menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
        .set_text('Disable Gap Fill')
 | 
			
		||||
        .set_icon('basket_unfill')
 | 
			
		||||
        .set_callback(function() {
 | 
			
		||||
          beestat.setting('runtime_thermostat_summary_gap_fill', false);
 | 
			
		||||
        }));
 | 
			
		||||
    } else {
 | 
			
		||||
      menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
        .set_text('Enable Gap Fill')
 | 
			
		||||
        .set_icon('basket_fill')
 | 
			
		||||
        .set_callback(function() {
 | 
			
		||||
          beestat.setting('runtime_thermostat_summary_gap_fill', true);
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (beestat.setting('runtime_thermostat_summary_smart_scale') === true) {
 | 
			
		||||
      menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
        .set_text('Disable Smart Scale')
 | 
			
		||||
        .set_icon('network_strength_off')
 | 
			
		||||
        .set_callback(function() {
 | 
			
		||||
          beestat.setting('runtime_thermostat_summary_smart_scale', false);
 | 
			
		||||
        }));
 | 
			
		||||
    } else {
 | 
			
		||||
      menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
        .set_text('Enable Smart Scale')
 | 
			
		||||
        .set_icon('network_strength_4')
 | 
			
		||||
        .set_callback(function() {
 | 
			
		||||
          beestat.setting('runtime_thermostat_summary_smart_scale', true);
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										145
									
								
								js/component/card/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								js/component/card/settings.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,145 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Setting
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.card.settings = function() {
 | 
			
		||||
  beestat.component.card.apply(this, arguments);
 | 
			
		||||
};
 | 
			
		||||
beestat.extend(beestat.component.card.settings, beestat.component.card);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decorate contents.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {rocket.Elements} parent Parent
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.card.settings.prototype.decorate_contents_ = function(parent) {
 | 
			
		||||
  const thermostat = beestat.cache.thermostat[
 | 
			
		||||
    beestat.setting('thermostat_id')
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  parent.appendChild(
 | 
			
		||||
    $.createElement('p')
 | 
			
		||||
      .style('font-weight', '400')
 | 
			
		||||
      .innerText('Thermostat Summary')
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Gap Fill
 | 
			
		||||
  const enable_gap_fill = new beestat.component.input.checkbox();
 | 
			
		||||
  enable_gap_fill
 | 
			
		||||
    .set_label('Enable Gap Fill')
 | 
			
		||||
    .set_value(beestat.setting('runtime_thermostat_summary_gap_fill'))
 | 
			
		||||
    .render(parent);
 | 
			
		||||
 | 
			
		||||
  enable_gap_fill.addEventListener('change', function() {
 | 
			
		||||
    enable_gap_fill.disable();
 | 
			
		||||
    beestat.setting(
 | 
			
		||||
      'runtime_thermostat_summary_gap_fill',
 | 
			
		||||
      enable_gap_fill.get_value(),
 | 
			
		||||
      function() {
 | 
			
		||||
        enable_gap_fill.enable();
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Smart Scale
 | 
			
		||||
  const enable_smart_scale = new beestat.component.input.checkbox();
 | 
			
		||||
  enable_smart_scale
 | 
			
		||||
    .set_label('Enable Smart Scale')
 | 
			
		||||
    .set_value(beestat.setting('runtime_thermostat_summary_smart_scale'))
 | 
			
		||||
    .render(parent);
 | 
			
		||||
 | 
			
		||||
  enable_smart_scale.addEventListener('change', function() {
 | 
			
		||||
    enable_smart_scale.disable();
 | 
			
		||||
    beestat.setting(
 | 
			
		||||
      'runtime_thermostat_summary_smart_scale',
 | 
			
		||||
      enable_smart_scale.get_value(),
 | 
			
		||||
      function() {
 | 
			
		||||
        enable_smart_scale.enable();
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  parent.appendChild(
 | 
			
		||||
    $.createElement('p')
 | 
			
		||||
      .style('font-weight', '400')
 | 
			
		||||
      .innerText('Temperature Profiles')
 | 
			
		||||
  );
 | 
			
		||||
  const ignore_solar_gain = new beestat.component.input.checkbox();
 | 
			
		||||
  const ignore_solar_gain_key = 'thermostat.' + thermostat.thermostat_id + '.profile.ignore_solar_gain';
 | 
			
		||||
  ignore_solar_gain
 | 
			
		||||
    .set_label('Ignore Solar Gain')
 | 
			
		||||
    .set_value(beestat.setting(ignore_solar_gain_key))
 | 
			
		||||
    .render(parent);
 | 
			
		||||
 | 
			
		||||
  ignore_solar_gain.addEventListener('change', function() {
 | 
			
		||||
    ignore_solar_gain.disable();
 | 
			
		||||
    beestat.setting(
 | 
			
		||||
      ignore_solar_gain_key,
 | 
			
		||||
      ignore_solar_gain.get_value(),
 | 
			
		||||
      function() {
 | 
			
		||||
        new beestat.api()
 | 
			
		||||
          .add_call(
 | 
			
		||||
            'thermostat',
 | 
			
		||||
            'generate_profile',
 | 
			
		||||
            {
 | 
			
		||||
              'thermostat_id': thermostat.thermostat_id
 | 
			
		||||
            },
 | 
			
		||||
            undefined,
 | 
			
		||||
            undefined,
 | 
			
		||||
            undefined,
 | 
			
		||||
            // Clear cache
 | 
			
		||||
            true
 | 
			
		||||
          )
 | 
			
		||||
          .add_call(
 | 
			
		||||
            'thermostat',
 | 
			
		||||
            'update',
 | 
			
		||||
            {
 | 
			
		||||
              'attributes': {
 | 
			
		||||
                'thermostat_id': thermostat.thermostat_id,
 | 
			
		||||
                'profile': null
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
          .add_call(
 | 
			
		||||
            'thermostat',
 | 
			
		||||
            'read_id',
 | 
			
		||||
            {
 | 
			
		||||
              'attributes': {
 | 
			
		||||
                'inactive': 0
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            'thermostat'
 | 
			
		||||
          )
 | 
			
		||||
          .set_callback(function(response) {
 | 
			
		||||
            ignore_solar_gain.enable();
 | 
			
		||||
            beestat.cache.set('thermostat', response.thermostat);
 | 
			
		||||
          })
 | 
			
		||||
          .send();
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the title of the card.
 | 
			
		||||
 *
 | 
			
		||||
 * @return {string} The title.
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.card.settings.prototype.get_title_ = function() {
 | 
			
		||||
  return 'Settings';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decorate the menu
 | 
			
		||||
 *
 | 
			
		||||
 * @param {rocket.Elements} parent
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.card.settings.prototype.decorate_top_right_ = function(parent) {
 | 
			
		||||
  var menu = (new beestat.component.menu()).render(parent);
 | 
			
		||||
 | 
			
		||||
  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/9d01e7256390473ca8121d4098d91c9d');
 | 
			
		||||
    }));
 | 
			
		||||
};
 | 
			
		||||
@ -165,6 +165,7 @@ beestat.component.card.temperature_profiles.prototype.get_data_ = function() {
 | 
			
		||||
              parseFloat(x_fixed),
 | 
			
		||||
              profile.deltas[x_fixed]
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            y_min = Math.min(y_min, profile.deltas[x_fixed]);
 | 
			
		||||
            y_max = Math.max(y_max, profile.deltas[x_fixed]);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -184,6 +184,13 @@ beestat.component.header.prototype.decorate_ = function(parent) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
    .set_text('Settings')
 | 
			
		||||
    .set_icon('cog')
 | 
			
		||||
    .set_callback(function() {
 | 
			
		||||
      (new beestat.layer.settings()).render();
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
  menu.add_menu_item(new beestat.component.menu_item()
 | 
			
		||||
    .set_text('Log Out')
 | 
			
		||||
    .set_icon('exit_to_app')
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
 * Input parent class.
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input = function() {
 | 
			
		||||
  this.uuid_ = this.generate_uuid_();
 | 
			
		||||
  beestat.component.apply(this, arguments);
 | 
			
		||||
};
 | 
			
		||||
beestat.extend(beestat.component.input, beestat.component);
 | 
			
		||||
@ -48,3 +49,15 @@ beestat.component.input.prototype.set_ = function(key, value) {
 | 
			
		||||
 | 
			
		||||
  return this;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generate a UUID to uniquely identify an input.
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://stackoverflow.com/a/2117523
 | 
			
		||||
 * @return {string} The UUID;
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input.prototype.generate_uuid_ = function() {
 | 
			
		||||
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
 | 
			
		||||
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										85
									
								
								js/component/input/checkbox.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								js/component/input/checkbox.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Checkbox parent class.
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input.checkbox = function() {
 | 
			
		||||
  this.input_ = $.createElement('input');
 | 
			
		||||
 | 
			
		||||
  beestat.component.input.apply(this, arguments);
 | 
			
		||||
};
 | 
			
		||||
beestat.extend(beestat.component.input.checkbox, beestat.component.input);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Decorate
 | 
			
		||||
 *
 | 
			
		||||
 * @param {rocket.Elements} parent
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input.checkbox.prototype.decorate_ = function(parent) {
 | 
			
		||||
  var self = this;
 | 
			
		||||
 | 
			
		||||
  const div = $.createElement('div').addClass('checkbox');
 | 
			
		||||
  this.input_
 | 
			
		||||
    .setAttribute('id', this.uuid_)
 | 
			
		||||
    .setAttribute('type', 'checkbox');
 | 
			
		||||
  div.appendChild(this.input_);
 | 
			
		||||
 | 
			
		||||
  const label = $.createElement('label');
 | 
			
		||||
  label.setAttribute('for', this.uuid_);
 | 
			
		||||
  div.appendChild(label);
 | 
			
		||||
 | 
			
		||||
  const text_label = $.createElement('span')
 | 
			
		||||
    .style({
 | 
			
		||||
      'cursor': 'pointer',
 | 
			
		||||
      'margin-left': (beestat.style.size.gutter / 2)
 | 
			
		||||
    })
 | 
			
		||||
    .innerText(this.label_)
 | 
			
		||||
    .addEventListener('click', function() {
 | 
			
		||||
      self.input_[0].click();
 | 
			
		||||
      // self.input_.checked(!self.input_.checked());
 | 
			
		||||
    });
 | 
			
		||||
  div.appendChild(text_label);
 | 
			
		||||
 | 
			
		||||
  this.input_.addEventListener('change', function() {
 | 
			
		||||
    // console.log('input changed');
 | 
			
		||||
    self.dispatchEvent('change');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  parent.appendChild(div);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the value in the input field. This bypasses the set_ function to avoid
 | 
			
		||||
 * rerendering when the input value is set. It's unnecessary and can also
 | 
			
		||||
 * cause minor issues if you try to set the value, then do something else with
 | 
			
		||||
 * the input immediately after.
 | 
			
		||||
 *
 | 
			
		||||
 * This will not fire off the change event listener.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} value
 | 
			
		||||
 *
 | 
			
		||||
 * @return {beestat.component.input.checkbox} This.
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input.checkbox.prototype.set_value = function(value) {
 | 
			
		||||
  this.input_.checked(value);
 | 
			
		||||
  return this;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get the value in the input field.
 | 
			
		||||
 *
 | 
			
		||||
 * @return {string} The value in the input field.
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input.checkbox.prototype.get_value = function() {
 | 
			
		||||
  return this.input_.checked();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Set the checkbox label.
 | 
			
		||||
 *
 | 
			
		||||
 * @param {string} label
 | 
			
		||||
 *
 | 
			
		||||
 * @return {beestat.component.input.checkbox} This.
 | 
			
		||||
 */
 | 
			
		||||
beestat.component.input.checkbox.prototype.set_label = function(label) {
 | 
			
		||||
  this.label_ = label;
 | 
			
		||||
  return this;
 | 
			
		||||
};
 | 
			
		||||
@ -20,7 +20,7 @@ beestat.component.input.text = function() {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  beestat.component.apply(this, arguments);
 | 
			
		||||
  beestat.component.input.apply(this, arguments);
 | 
			
		||||
};
 | 
			
		||||
beestat.extend(beestat.component.input.text, beestat.component.input);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -44,6 +44,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
 | 
			
		||||
  echo '<script src="/js/layer/detail.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/layer/compare.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/layer/analyze.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/layer/settings.js"></script>' . PHP_EOL;
 | 
			
		||||
 | 
			
		||||
  // Component
 | 
			
		||||
  echo '<script src="/js/component.js"></script>' . PHP_EOL;
 | 
			
		||||
@ -64,6 +65,7 @@ if($setting->get('environment') === 'dev' || $setting->get('environment') === 'd
 | 
			
		||||
  echo '<script src="/js/component/card/system.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/card/temperature_profiles.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/card/metrics.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/card/settings.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/chart.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/chart/runtime_thermostat_summary.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/chart/temperature_profiles.js"></script>' . PHP_EOL;
 | 
			
		||||
@ -95,6 +97,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/input.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/input/text.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/input/checkbox.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/button.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/button_group.js"></script>' . PHP_EOL;
 | 
			
		||||
  echo '<script src="/js/component/title.js"></script>' . PHP_EOL;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										51
									
								
								js/layer/settings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								js/layer/settings.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Setting layer.
 | 
			
		||||
 */
 | 
			
		||||
beestat.layer.settings = function() {
 | 
			
		||||
  beestat.layer.apply(this, arguments);
 | 
			
		||||
};
 | 
			
		||||
beestat.extend(beestat.layer.settings, beestat.layer);
 | 
			
		||||
 | 
			
		||||
beestat.layer.settings.prototype.decorate_ = function(parent) {
 | 
			
		||||
  /*
 | 
			
		||||
   * Set the overflow on the body so the scrollbar is always present so
 | 
			
		||||
   * highcharts graphs render properly.
 | 
			
		||||
   */
 | 
			
		||||
  $('body').style({
 | 
			
		||||
    'overflow-y': 'scroll',
 | 
			
		||||
    'background': beestat.style.color.bluegray.light,
 | 
			
		||||
    'padding': '0 ' + beestat.style.size.gutter + 'px'
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  (new beestat.component.header('setting')).render(parent);
 | 
			
		||||
 | 
			
		||||
  // All the cards
 | 
			
		||||
  const cards = [];
 | 
			
		||||
 | 
			
		||||
  if (window.is_demo === true) {
 | 
			
		||||
    cards.push([
 | 
			
		||||
      {
 | 
			
		||||
        'card': new beestat.component.card.demo(),
 | 
			
		||||
        'size': 12
 | 
			
		||||
      }
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Settings
 | 
			
		||||
  cards.push([
 | 
			
		||||
    {
 | 
			
		||||
      'card': new beestat.component.card.settings(),
 | 
			
		||||
      'size': 12
 | 
			
		||||
    }
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  // Footer
 | 
			
		||||
  cards.push([
 | 
			
		||||
    {
 | 
			
		||||
      'card': new beestat.component.card.footer(),
 | 
			
		||||
      'size': 12
 | 
			
		||||
    }
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  (new beestat.component.layout(cards)).render(parent);
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user