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

Added settings page and all the things that go along with it.

This commit is contained in:
Jon Ziebell 2021-12-14 20:23:56 -05:00
parent 65cfc07220
commit 06073bfc25
17 changed files with 691 additions and 107 deletions

View File

@ -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.
*

View File

@ -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,31 +147,37 @@ 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();
$api_cache = $api_cache_instance->retrieve($this);
if($api_cache !== null) {
// 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']));
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 {
// Else just run the API call, then cache it.
$this->response = call_user_func_array(
[$resource_instance, $this->method],
$this->arguments
);
$api_cache = $api_cache_instance->retrieve($this);
$api_cache = $api_cache_instance->cache(
$this,
$this->response,
$this->resource::$cache[$this->method]
);
$this->cached_until = date('Y-m-d H:i:s', strtotime($api_cache['expires_at']));
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']));
} else {
// Else just run the API call, then cache it.
$this->response = call_user_func_array(
[$resource_instance, $this->method],
$this->arguments
);
if($this->bypass_cache_write === false) {
$api_cache = $api_cache_instance->cache(
$this,
$this->response,
$this->resource::$cache[$this->method]
);
$this->cached_until = date('Y-m-d H:i:s', strtotime($api_cache['expires_at']));
}
}
}
}
else { // Not cacheable

View File

@ -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,12 +331,15 @@ 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
) {
continue;
// 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) {

View File

@ -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
*/

View File

@ -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.
*

View File

@ -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"; }

View File

@ -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;
}

View File

@ -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 {
settings = {};
settings[key] = opt_value;
key = argument_1;
if (opt_value !== undefined) {
settings = {};
settings[key] = opt_value;
}
}
mode = (settings !== undefined || opt_value !== undefined) ? 'set' : 'get';
// 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);
}
var api = new beestat.api();
// 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;
};

View File

@ -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()

View 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');
}));
};

View File

@ -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]);
}

View File

@ -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')

View File

@ -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)
);
};

View 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;
};

View File

@ -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);

View File

@ -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
View 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);
};