From 8bd23dd45787d41dc5c12d241acc2912faae77a6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 9 Jun 2021 18:59:06 +1200 Subject: [PATCH 001/105] Bump version to v1.19.0b1 --- esphome/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index b50b60a204..159a74adc2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,8 +1,8 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 20 -PATCH_VERSION = "0-dev" +MINOR_VERSION = 19 +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 139db58a6652c670d3222ac04f8260c173316710 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Wed, 9 Jun 2021 17:01:05 +0200 Subject: [PATCH 002/105] Simplify LightCall validation (#1874) --- esphome/components/light/light_state.cpp | 43 +++++++----------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 4e450f6ccb..749dea5419 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -407,7 +407,7 @@ LightColorValues LightCall::validate_() { this->color_temperature_.reset(); } - // sets RGB to 100% if only White specified + // If white channel is specified, set RGB to white color (when interlock is enabled) if (this->white_.has_value()) { if (traits.get_supports_color_interlock()) { if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { @@ -415,7 +415,7 @@ LightColorValues LightCall::validate_() { this->green_ = optional(1.0f); this->blue_ = optional(1.0f); } - // make white values binary aka 0.0f or 1.0f...this allows brightness to do its job + // make white values binary aka 0.0f or 1.0f... this allows brightness to do its job if (*this->white_ > 0.0f) { this->white_ = optional(1.0f); } else { @@ -423,44 +423,27 @@ LightColorValues LightCall::validate_() { } } } - // White to 0% if (exclusively) setting any RGB value that isn't 255,255,255 + // If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled) else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { if (traits.get_supports_color_interlock()) { - if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f && - traits.get_supports_rgb_white_value() && traits.get_supports_color_interlock()) { + if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { this->white_ = optional(1.0f); - } else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) { + } else { this->white_ = optional(0.0f); } } } - // if changing Kelvin alone, change to white light + // If only a color temperature is specified, change to white light else if (this->color_temperature_.has_value()) { - if (!traits.get_supports_color_interlock()) { - if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - } - } - // if setting Kelvin from color (i.e. switching to white light), set White to 100% + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(1.0f); + + // if setting color temperature from color (i.e. switching to white light), set White to 100% auto cv = this->parent_->remote_values; bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f; - bool now_white = *this->red_ == 1.0f && *this->blue_ == 1.0f && *this->green_ == 1.0f; - if (traits.get_supports_color_interlock()) { - if (cv.get_white() < 1.0f) { - this->white_ = optional(1.0f); - } - - if (was_color && !this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); - } - } else { - if (!this->white_.has_value() && was_color && now_white) { - this->white_ = optional(1.0f); - } + if (traits.get_supports_color_interlock() || was_color) { + this->white_ = optional(1.0f); } } From 0db4815f3de8a8b9dac480ad679798102b447bd6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Jun 2021 14:04:39 +1200 Subject: [PATCH 003/105] BLE loop use (#1882) --- esphome/components/esp32_ble/ble.cpp | 78 +++++++---------- esphome/components/esp32_ble/ble.h | 10 +-- .../esp32_ble/ble_characteristic.cpp | 48 +++++++--- .../components/esp32_ble/ble_characteristic.h | 16 +++- .../components/esp32_ble/ble_descriptor.cpp | 15 ++-- esphome/components/esp32_ble/ble_descriptor.h | 13 ++- esphome/components/esp32_ble/ble_server.cpp | 87 +++++++++++-------- esphome/components/esp32_ble/ble_server.h | 18 +++- esphome/components/esp32_ble/ble_service.cpp | 61 ++++++++----- esphome/components/esp32_ble/ble_service.h | 30 +++++-- esphome/components/esp32_ble/queue.h | 29 +++++-- .../esp32_improv/esp32_improv_component.cpp | 25 ++++-- .../esp32_improv/esp32_improv_component.h | 5 +- esphome/components/wifi/wifi_component.cpp | 2 +- 14 files changed, 272 insertions(+), 165 deletions(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 2200d3cef1..889c793017 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -20,14 +20,13 @@ void ESP32BLE::setup() { global_ble = this; ESP_LOGCONFIG(TAG, "Setting up BLE..."); - xTaskCreatePinnedToCore(ESP32BLE::ble_core_task_, - "ble_task", // name - 10000, // stack size - nullptr, // input params - 1, // priority - nullptr, // handle, not needed - 0 // core - ); + if (!ble_setup_()) { + ESP_LOGE(TAG, "BLE could not be set up"); + this->mark_failed(); + return; + } + + ESP_LOGD(TAG, "BLE setup complete"); } void ESP32BLE::mark_failed() { @@ -37,23 +36,6 @@ void ESP32BLE::mark_failed() { } } -bool ESP32BLE::can_proceed() { return this->ready_; } - -void ESP32BLE::ble_core_task_(void *params) { - if (!ble_setup_()) { - ESP_LOGE(TAG, "BLE could not be set up"); - global_ble->mark_failed(); - return; - } - - global_ble->ready_ = true; - ESP_LOGD(TAG, "BLE Setup complete"); - - while (true) { - vTaskDelay(1000 / portTICK_PERIOD_MS); - } -} - bool ESP32BLE::ble_setup_() { esp_err_t err = nvs_flash_init(); if (err != ESP_OK) { @@ -84,7 +66,7 @@ bool ESP32BLE::ble_setup_() { return false; } - if (global_ble->has_server()) { + if (this->has_server()) { err = esp_ble_gatts_register_callback(ESP32BLE::gatts_event_handler); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_register_callback failed: %d", err); @@ -92,7 +74,7 @@ bool ESP32BLE::ble_setup_() { } } - if (global_ble->has_client()) { + if (this->has_client()) { err = esp_ble_gattc_register_callback(ESP32BLE::gattc_event_handler); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gattc_register_callback failed: %d", err); @@ -119,27 +101,28 @@ bool ESP32BLE::ble_setup_() { return true; } -// void ESP32BLE::loop() { -// BLEEvent *ble_event = this->ble_events_.pop(); -// while (ble_event != nullptr) { -// switch (ble_event->type_) { -// case ble_event->GATTS: -// this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if, -// &ble_event->event_.gatts.gatts_param); -// break; -// case ble_event->GAP: -// this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); -// break; -// default: -// break; -// } -// delete ble_event; -// ble_event = this->ble_events_.pop(); -// } -// } +void ESP32BLE::loop() { + BLEEvent *ble_event = this->ble_events_.pop(); + while (ble_event != nullptr) { + switch (ble_event->type_) { + case ble_event->GATTS: + this->real_gatts_event_handler_(ble_event->event_.gatts.gatts_event, ble_event->event_.gatts.gatts_if, + &ble_event->event_.gatts.gatts_param); + break; + case ble_event->GAP: + this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); + break; + default: + break; + } + delete ble_event; + ble_event = this->ble_events_.pop(); + } +} void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - global_ble->real_gap_event_handler_(event, param); + BLEEvent *new_event = new BLEEvent(event, param); + global_ble->ble_events_.push(new_event); } void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { @@ -152,7 +135,8 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { - global_ble->real_gatts_event_handler_(event, gatts_if, param); + BLEEvent *new_event = new BLEEvent(event, gatts_if, param); + global_ble->ble_events_.push(new_event); } void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 8850f10d85..27cb9a2092 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -23,17 +23,14 @@ typedef struct { class ESP32BLE : public Component { public: void setup() override; - // void loop() override; + void loop() override; void dump_config() override; float get_setup_priority() const override; void mark_failed() override; - bool can_proceed() override; bool has_server() { return this->server_ != nullptr; } bool has_client() { return false; } - bool is_ready() { return this->ready_; } - void set_server(BLEServer *server) { this->server_ = server; } protected: @@ -45,10 +42,7 @@ class ESP32BLE : public Component { void real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); void real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - static void ble_core_task_(void *params); - static bool ble_setup_(); - - bool ready_{false}; + bool ble_setup_(); BLEServer *server_{nullptr}; Queue ble_events_; diff --git a/esphome/components/esp32_ble/ble_characteristic.cpp b/esphome/components/esp32_ble/ble_characteristic.cpp index 240b57eb38..127f973146 100644 --- a/esphome/components/esp32_ble/ble_characteristic.cpp +++ b/esphome/components/esp32_ble/ble_characteristic.cpp @@ -13,10 +13,8 @@ static const char *TAG = "esp32_ble.characteristic"; BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) { this->set_value_lock_ = xSemaphoreCreateBinary(); - this->create_lock_ = xSemaphoreCreateBinary(); - xSemaphoreGive(this->set_value_lock_); - xSemaphoreGive(this->create_lock_); + this->properties_ = (esp_gatt_char_prop_t) 0; this->set_broadcast_property((properties & PROPERTY_BROADCAST) != 0); @@ -100,12 +98,11 @@ void BLECharacteristic::notify(bool notification) { void BLECharacteristic::add_descriptor(BLEDescriptor *descriptor) { this->descriptors_.push_back(descriptor); } -bool BLECharacteristic::do_create(BLEService *service) { +void BLECharacteristic::do_create(BLEService *service) { this->service_ = service; esp_attr_control_t control; control.auto_rsp = ESP_GATT_RSP_BY_APP; - xSemaphoreTake(this->create_lock_, portMAX_DELAY); ESP_LOGV(TAG, "Creating characteristic - %s", this->uuid_.to_string().c_str()); esp_bt_uuid_t uuid = this->uuid_.get_uuid(); @@ -114,15 +111,39 @@ bool BLECharacteristic::do_create(BLEService *service) { if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_add_char failed: %d", err); + return; + } + + this->state_ = CREATING; +} + +bool BLECharacteristic::is_created() { + if (this->state_ == CREATED) + return true; + + if (this->state_ != CREATING_DEPENDENTS) return false; - } - - xSemaphoreWait(this->create_lock_, portMAX_DELAY); + bool created = true; for (auto *descriptor : this->descriptors_) { - descriptor->do_create(this); + created &= descriptor->is_created(); } - return true; + if (created) + this->state_ = CREATED; + return this->state_ == CREATED; +} + +bool BLECharacteristic::is_failed() { + if (this->state_ == FAILED) + return true; + + bool failed = false; + for (auto *descriptor : this->descriptors_) { + failed |= descriptor->is_failed(); + } + if (failed) + this->state_ = FAILED; + return this->state_ == FAILED; } void BLECharacteristic::set_broadcast_property(bool value) { @@ -168,7 +189,12 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt case ESP_GATTS_ADD_CHAR_EVT: { if (this->uuid_ == ESPBTUUID::from_uuid(param->add_char.char_uuid)) { this->handle_ = param->add_char.attr_handle; - xSemaphoreGive(this->create_lock_); + + for (auto *descriptor : this->descriptors_) { + descriptor->do_create(this); + } + + this->state_ = CREATING_DEPENDENTS; } break; } diff --git a/esphome/components/esp32_ble/ble_characteristic.h b/esphome/components/esp32_ble/ble_characteristic.h index 076f5c1a5f..648cfb939d 100644 --- a/esphome/components/esp32_ble/ble_characteristic.h +++ b/esphome/components/esp32_ble/ble_characteristic.h @@ -42,10 +42,10 @@ class BLECharacteristic { void notify(bool notification = true); - bool do_create(BLEService *service); + void do_create(BLEService *service); void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); - void on_write(const std::function &)> &func) { this->on_write_ = func; } + void on_write(const std::function &)> &&func) { this->on_write_ = std::move(func); } void add_descriptor(BLEDescriptor *descriptor); @@ -60,6 +60,9 @@ class BLECharacteristic { static const uint32_t PROPERTY_INDICATE = 1 << 4; static const uint32_t PROPERTY_WRITE_NR = 1 << 5; + bool is_created(); + bool is_failed(); + protected: bool write_event_{false}; BLEService *service_; @@ -70,13 +73,20 @@ class BLECharacteristic { uint16_t value_read_offset_{0}; std::vector value_; SemaphoreHandle_t set_value_lock_; - SemaphoreHandle_t create_lock_; std::vector descriptors_; std::function &)> on_write_; esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + + enum State : uint8_t { + FAILED = 0x00, + INIT, + CREATING, + CREATING_DEPENDENTS, + CREATED, + } state_{INIT}; }; } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/ble_descriptor.cpp b/esphome/components/esp32_ble/ble_descriptor.cpp index 4c4e3968e8..9738cc2fe7 100644 --- a/esphome/components/esp32_ble/ble_descriptor.cpp +++ b/esphome/components/esp32_ble/ble_descriptor.cpp @@ -16,30 +16,25 @@ BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) { this->value_.attr_len = 0; this->value_.attr_max_len = max_len; this->value_.attr_value = (uint8_t *) malloc(max_len); - - this->create_lock_ = xSemaphoreCreateBinary(); - xSemaphoreGive(this->create_lock_); } BLEDescriptor::~BLEDescriptor() { free(this->value_.attr_value); } -bool BLEDescriptor::do_create(BLECharacteristic *characteristic) { +void BLEDescriptor::do_create(BLECharacteristic *characteristic) { this->characteristic_ = characteristic; esp_attr_control_t control; control.auto_rsp = ESP_GATT_AUTO_RSP; - xSemaphoreTake(this->create_lock_, portMAX_DELAY); ESP_LOGV(TAG, "Creating descriptor - %s", this->uuid_.to_string().c_str()); esp_bt_uuid_t uuid = this->uuid_.get_uuid(); esp_err_t err = esp_ble_gatts_add_char_descr(this->characteristic_->get_service()->get_handle(), &uuid, this->permissions_, &this->value_, &control); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_add_char_descr failed: %d", err); - return false; + this->state_ = FAILED; + return; } - xSemaphoreWait(this->create_lock_, portMAX_DELAY); - - return true; + this->state_ = CREATING; } void BLEDescriptor::set_value(const std::string &value) { this->set_value((uint8_t *) value.data(), value.length()); } @@ -60,7 +55,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ this->characteristic_->get_service()->get_handle() == param->add_char_descr.service_handle && this->characteristic_ == this->characteristic_->get_service()->get_last_created_characteristic()) { this->handle_ = param->add_char_descr.attr_handle; - xSemaphoreGive(this->create_lock_); + this->state_ = CREATED; } break; } diff --git a/esphome/components/esp32_ble/ble_descriptor.h b/esphome/components/esp32_ble/ble_descriptor.h index ffbb9645bd..bc7b48e331 100644 --- a/esphome/components/esp32_ble/ble_descriptor.h +++ b/esphome/components/esp32_ble/ble_descriptor.h @@ -16,22 +16,31 @@ class BLEDescriptor { public: BLEDescriptor(ESPBTUUID uuid, uint16_t max_len = 100); virtual ~BLEDescriptor(); - bool do_create(BLECharacteristic *characteristic); + void do_create(BLECharacteristic *characteristic); void set_value(const std::string &value); void set_value(const uint8_t *data, size_t length); void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + bool is_created() { return this->state_ == CREATED; } + bool is_failed() { return this->state_ == FAILED; } + protected: BLECharacteristic *characteristic_{nullptr}; ESPBTUUID uuid_; uint16_t handle_{0xFFFF}; - SemaphoreHandle_t create_lock_; esp_attr_value_t value_; esp_gatt_perm_t permissions_ = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + + enum State : uint8_t { + FAILED = 0x00, + INIT, + CREATING, + CREATED, + } state_{INIT}; }; } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/ble_server.cpp b/esphome/components/esp32_ble/ble_server.cpp index a63312dd62..9d5be46113 100644 --- a/esphome/components/esp32_ble/ble_server.cpp +++ b/esphome/components/esp32_ble/ble_server.cpp @@ -18,7 +18,7 @@ namespace esp32_ble { static const char *TAG = "esp32_ble.server"; -static const uint16_t DEVICE_INFORMATION_SERVICE_UUID = 0x180A; +static const uint16_t device_information_service__UUID = 0x180A; static const uint16_t MODEL_UUID = 0x2A24; static const uint16_t VERSION_UUID = 0x2A26; static const uint16_t MANUFACTURER_UUID = 0x2A29; @@ -32,59 +32,78 @@ void BLEServer::setup() { ESP_LOGD(TAG, "Setting up BLE Server..."); global_ble_server = this; - this->register_lock_ = xSemaphoreCreateBinary(); - xSemaphoreGive(this->register_lock_); + this->advertising_ = new BLEAdvertising(); - - this->setup_server_(); - - for (auto *component : this->service_components_) { - component->setup_service(); - } - - ESP_LOGD(TAG, "BLE Server set up complete..."); } -void BLEServer::setup_server_() { - xSemaphoreTake(this->register_lock_, portMAX_DELAY); - esp_err_t err = esp_ble_gatts_app_register(0); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ble_gatts_app_register failed: %d", err); - this->mark_failed(); - return; +void BLEServer::loop() { + switch (this->state_) { + case RUNNING: + return; + + case INIT: { + esp_err_t err = esp_ble_gatts_app_register(0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gatts_app_register failed: %d", err); + this->mark_failed(); + return; + } + this->state_ = REGISTERING; + break; + } + case REGISTERING: { + if (this->registered_) { + this->device_information_service_ = this->create_service(device_information_service__UUID); + + this->create_device_characteristics_(); + + this->advertising_->set_scan_response(true); + this->advertising_->set_min_preferred_interval(0x06); + this->advertising_->start(); + + this->state_ = STARTING_SERVICE; + } + break; + } + case STARTING_SERVICE: { + if (this->device_information_service_->is_running()) { + for (auto *component : this->service_components_) { + component->setup_service(); + } + this->state_ = SETTING_UP_COMPONENT_SERVICES; + } else if (!this->device_information_service_->is_starting()) { + this->device_information_service_->start(); + } + break; + } + case SETTING_UP_COMPONENT_SERVICES: { + this->state_ = RUNNING; + this->can_proceed_ = true; + ESP_LOGD(TAG, "BLE server setup successfully"); + break; + } } - xSemaphoreWait(this->register_lock_, portMAX_DELAY); - - this->device_information_service = this->create_service(DEVICE_INFORMATION_SERVICE_UUID); - - this->create_device_characteristics_(); - - this->advertising_->set_scan_response(true); - this->advertising_->set_min_preferred_interval(0x06); - this->advertising_->start(); - - this->device_information_service->start(); } bool BLEServer::create_device_characteristics_() { if (this->model_.has_value()) { BLECharacteristic *model = - this->device_information_service->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ); + this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ); model->set_value(this->model_.value()); } else { #ifdef ARDUINO_BOARD BLECharacteristic *model = - this->device_information_service->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ); + this->device_information_service_->create_characteristic(MODEL_UUID, BLECharacteristic::PROPERTY_READ); model->set_value(ARDUINO_BOARD); #endif } BLECharacteristic *version = - this->device_information_service->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ); + this->device_information_service_->create_characteristic(VERSION_UUID, BLECharacteristic::PROPERTY_READ); version->set_value("ESPHome " ESPHOME_VERSION); BLECharacteristic *manufacturer = - this->device_information_service->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ); + this->device_information_service_->create_characteristic(MANUFACTURER_UUID, BLECharacteristic::PROPERTY_READ); manufacturer->set_value(this->manufacturer_); return true; @@ -134,7 +153,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga } case ESP_GATTS_REG_EVT: { this->gatts_if_ = gatts_if; - xSemaphoreGive(this->register_lock_); + this->registered_ = true; break; } default: diff --git a/esphome/components/esp32_ble/ble_server.h b/esphome/components/esp32_ble/ble_server.h index 0713816ad3..5657773446 100644 --- a/esphome/components/esp32_ble/ble_server.h +++ b/esphome/components/esp32_ble/ble_server.h @@ -24,13 +24,17 @@ class BLEServiceComponent { virtual void setup_service(); virtual void on_client_connect(){}; virtual void on_client_disconnect(){}; + virtual void start(); + virtual void stop(); }; class BLEServer : public Component { public: void setup() override; + void loop() override; void dump_config() override; float get_setup_priority() const override; + bool can_proceed() override { return this->can_proceed_; } void teardown(); @@ -53,27 +57,35 @@ class BLEServer : public Component { protected: bool create_device_characteristics_(); - void setup_server_(); void add_client_(uint16_t conn_id, void *client) { this->clients_.insert(std::pair(conn_id, client)); } bool remove_client_(uint16_t conn_id) { return this->clients_.erase(conn_id) > 0; } + bool can_proceed_{false}; + std::string manufacturer_; optional model_; esp_gatt_if_t gatts_if_{0}; + bool registered_{false}; BLEAdvertising *advertising_; uint32_t connected_clients_{0}; std::map clients_; std::vector services_; - BLEService *device_information_service; + BLEService *device_information_service_; std::vector service_components_; - SemaphoreHandle_t register_lock_; + enum State : uint8_t { + INIT = 0x00, + REGISTERING, + STARTING_SERVICE, + SETTING_UP_COMPONENT_SERVICES, + RUNNING, + } state_{INIT}; }; extern BLEServer *global_ble_server; diff --git a/esphome/components/esp32_ble/ble_service.cpp b/esphome/components/esp32_ble/ble_service.cpp index af8fe00a1d..4b85182696 100644 --- a/esphome/components/esp32_ble/ble_service.cpp +++ b/esphome/components/esp32_ble/ble_service.cpp @@ -10,15 +10,7 @@ namespace esp32_ble { static const char *TAG = "esp32_ble.service"; BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) - : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) { - this->create_lock_ = xSemaphoreCreateBinary(); - this->start_lock_ = xSemaphoreCreateBinary(); - this->stop_lock_ = xSemaphoreCreateBinary(); - - xSemaphoreGive(this->create_lock_); - xSemaphoreGive(this->start_lock_); - xSemaphoreGive(this->stop_lock_); -} + : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {} BLEService::~BLEService() { for (auto &chr : this->characteristics_) @@ -47,10 +39,9 @@ BLECharacteristic *BLEService::create_characteristic(ESPBTUUID uuid, esp_gatt_ch return characteristic; } -bool BLEService::do_create(BLEServer *server) { +void BLEService::do_create(BLEServer *server) { this->server_ = server; - xSemaphoreTake(this->create_lock_, portMAX_DELAY); esp_gatt_srvc_id_t srvc_id; srvc_id.is_primary = true; srvc_id.id.inst_id = this->inst_id_; @@ -59,36 +50,58 @@ bool BLEService::do_create(BLEServer *server) { esp_err_t err = esp_ble_gatts_create_service(server->get_gatts_if(), &srvc_id, this->num_handles_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_create_service failed: %d", err); - return false; + this->init_state_ = FAILED; + return; } - xSemaphoreWait(this->create_lock_, portMAX_DELAY); + this->init_state_ = CREATING; +} +bool BLEService::do_create_characteristics_() { + if (this->created_characteristic_count_ >= this->characteristics_.size() && + (this->last_created_characteristic_ == nullptr || this->last_created_characteristic_->is_created())) + return false; // Signifies there are no characteristics, or they are all finished being created. + + if (this->last_created_characteristic_ != nullptr && !this->last_created_characteristic_->is_created()) + return true; // Signifies that the previous characteristic is still being created. + + auto *characteristic = this->characteristics_[this->created_characteristic_count_++]; + this->last_created_characteristic_ = characteristic; + characteristic->do_create(this); return true; } void BLEService::start() { - for (auto *characteristic : this->characteristics_) { - this->last_created_characteristic_ = characteristic; - characteristic->do_create(this); - } + if (this->do_create_characteristics_()) + return; - xSemaphoreTake(this->start_lock_, portMAX_DELAY); esp_err_t err = esp_ble_gatts_start_service(this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_start_service failed: %d", err); return; } - xSemaphoreWait(this->start_lock_, portMAX_DELAY); + this->running_state_ = STARTING; } void BLEService::stop() { - xSemaphoreTake(this->stop_lock_, portMAX_DELAY); esp_err_t err = esp_ble_gatts_stop_service(this->handle_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gatts_stop_service failed: %d", err); return; } - xSemaphoreWait(this->stop_lock_, portMAX_DELAY); + this->running_state_ = STOPPING; +} + +bool BLEService::is_created() { return this->init_state_ == CREATED; } +bool BLEService::is_failed() { + if (this->init_state_ == FAILED) + return true; + bool failed = false; + for (auto *characteristic : this->characteristics_) + failed |= characteristic->is_failed(); + + if (failed) + this->init_state_ = FAILED; + return this->init_state_ == FAILED; } void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, @@ -98,19 +111,19 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g if (this->uuid_ == ESPBTUUID::from_uuid(param->create.service_id.id.uuid) && this->inst_id_ == param->create.service_id.id.inst_id) { this->handle_ = param->create.service_handle; - xSemaphoreGive(this->create_lock_); + this->init_state_ = CREATED; } break; } case ESP_GATTS_START_EVT: { if (param->start.service_handle == this->handle_) { - xSemaphoreGive(this->start_lock_); + this->running_state_ = RUNNING; } break; } case ESP_GATTS_STOP_EVT: { if (param->start.service_handle == this->handle_) { - xSemaphoreGive(this->stop_lock_); + this->running_state_ = STOPPED; } break; } diff --git a/esphome/components/esp32_ble/ble_service.h b/esphome/components/esp32_ble/ble_service.h index 9a88d4e1e7..a539631846 100644 --- a/esphome/components/esp32_ble/ble_service.h +++ b/esphome/components/esp32_ble/ble_service.h @@ -33,26 +33,44 @@ class BLEService { BLEServer *get_server() { return this->server_; } - bool do_create(BLEServer *server); + void do_create(BLEServer *server); void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void start(); void stop(); - protected: - bool errored_{false}; + bool is_created(); + bool is_failed(); + bool is_running() { return this->running_state_ == RUNNING; } + bool is_starting() { return this->running_state_ == STARTING; } + + protected: std::vector characteristics_; BLECharacteristic *last_created_characteristic_{nullptr}; + uint32_t created_characteristic_count_{0}; BLEServer *server_; ESPBTUUID uuid_; uint16_t num_handles_; uint16_t handle_{0xFFFF}; uint8_t inst_id_; - SemaphoreHandle_t create_lock_; - SemaphoreHandle_t start_lock_; - SemaphoreHandle_t stop_lock_; + bool do_create_characteristics_(); + + enum InitState : uint8_t { + FAILED = 0x00, + INIT, + CREATING, + CREATING_DEPENDENTS, + CREATED, + } init_state_{INIT}; + + enum RunningState : uint8_t { + STARTING, + RUNNING, + STOPPING, + STOPPED, + } running_state_{STOPPED}; }; } // namespace esp32_ble diff --git a/esphome/components/esp32_ble/queue.h b/esphome/components/esp32_ble/queue.h index f5c861a917..3c87a90f22 100644 --- a/esphome/components/esp32_ble/queue.h +++ b/esphome/components/esp32_ble/queue.h @@ -53,7 +53,7 @@ template class Queue { SemaphoreHandle_t m; }; -// Received GAP and GATTC events are only queued, and get processed in the main loop(). +// Received GAP, GATTC and GATTS events are only queued, and get processed in the main loop(). // This class stores each event in a single type. class BLEEvent { public: @@ -68,9 +68,18 @@ class BLEEvent { this->event_.gattc.gattc_if = i; memcpy(&this->event_.gattc.gattc_param, p, sizeof(esp_ble_gattc_cb_param_t)); // Need to also make a copy of notify event data. - if (e == ESP_GATTC_NOTIFY_EVT) { - memcpy(this->event_.gattc.notify_data, p->notify.value, p->notify.value_len); - this->event_.gattc.gattc_param.notify.value = this->event_.gattc.notify_data; + switch (e) { + case ESP_GATTC_NOTIFY_EVT: + memcpy(this->event_.gattc.data, p->notify.value, p->notify.value_len); + this->event_.gattc.gattc_param.notify.value = this->event_.gattc.data; + break; + case ESP_GATTC_READ_CHAR_EVT: + case ESP_GATTC_READ_DESCR_EVT: + memcpy(this->event_.gattc.data, p->read.value, p->read.value_len); + this->event_.gattc.gattc_param.read.value = this->event_.gattc.data; + break; + default: + break; } this->type_ = GATTC; }; @@ -79,6 +88,15 @@ class BLEEvent { this->event_.gatts.gatts_event = e; this->event_.gatts.gatts_if = i; memcpy(&this->event_.gatts.gatts_param, p, sizeof(esp_ble_gatts_cb_param_t)); + // Need to also make a copy of write data. + switch (e) { + case ESP_GATTS_WRITE_EVT: + memcpy(this->event_.gatts.data, p->write.value, p->write.len); + this->event_.gatts.gatts_param.write.value = this->event_.gatts.data; + break; + default: + break; + } this->type_ = GATTS; }; @@ -92,13 +110,14 @@ class BLEEvent { esp_gattc_cb_event_t gattc_event; esp_gatt_if_t gattc_if; esp_ble_gattc_cb_param_t gattc_param; - uint8_t notify_data[64]; + uint8_t data[64]; } gattc; struct gatts_event { esp_gatts_cb_event_t gatts_event; esp_gatt_if_t gatts_if; esp_ble_gatts_cb_param_t gatts_param; + uint8_t data[64]; } gatts; } event_; enum ble_event_t : uint8_t { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index a407d819b7..491f26f46e 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -14,7 +14,9 @@ ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } void ESP32ImprovComponent::setup_service() { this->service_ = esp32_ble::global_ble_server->create_service(improv::SERVICE_UUID, true); +} +void ESP32ImprovComponent::setup_characteristics() { this->status_ = this->service_->create_characteristic( improv::STATUS_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); esp32_ble::BLEDescriptor *status_descriptor = new esp32_ble::BLE2902(); @@ -62,16 +64,21 @@ void ESP32ImprovComponent::loop() { if (this->status_indicator_ != nullptr) this->status_indicator_->turn_off(); + if (this->service_->is_created() && !this->setup_complete_) { + this->setup_characteristics(); + } + if (this->should_start_ && this->setup_complete_) { - ESP_LOGD(TAG, "Starting Improv service..."); + if (this->service_->is_running()) { + this->service_->get_server()->get_advertising()->start(); - this->service_->start(); - this->service_->get_server()->get_advertising()->start(); - - this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); - this->set_error_(improv::ERROR_NONE); - this->should_start_ = false; - ESP_LOGD(TAG, "Service started!"); + this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); + this->set_error_(improv::ERROR_NONE); + this->should_start_ = false; + ESP_LOGD(TAG, "Service started!"); + } else { + this->service_->start(); + } } break; case improv::STATE_AWAITING_AUTHORIZATION: { @@ -191,7 +198,7 @@ void ESP32ImprovComponent::start() { this->should_start_ = true; } -void ESP32ImprovComponent::end() { +void ESP32ImprovComponent::stop() { this->set_timeout("end-service", 1000, [this] { this->service_->stop(); this->set_state_(improv::STATE_STOPPED); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 498a443ec3..e9b0c72ecd 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -21,11 +21,12 @@ class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceCompo void dump_config() override; void loop() override; void setup_service() override; + void setup_characteristics(); void on_client_disconnect() override; float get_setup_priority() const override; - void start(); - void end(); + void start() override; + void stop() override; bool is_active() const { return this->state_ != improv::STATE_STOPPED; } void set_authorizer(binary_sensor::BinarySensor *authorizer) { this->authorizer_ = authorizer; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index da1f6f85a7..de7ffb4f09 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -508,7 +508,7 @@ void WiFiComponent::check_connecting_finished() { } #ifdef USE_IMPROV if (this->is_esp32_improv_active_()) { - esp32_improv::global_improv_component->end(); + esp32_improv::global_improv_component->stop(); } #endif From 8b737aabd9ad7fd675e00aa53003ae67983e6efb Mon Sep 17 00:00:00 2001 From: Geoff Davis Date: Wed, 9 Jun 2021 19:05:27 -0700 Subject: [PATCH 004/105] Add support for waveshare_epaper 1.54v2 (#1843) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/waveshare_epaper/display.py | 3 ++- esphome/components/waveshare_epaper/waveshare_epaper.cpp | 9 ++++++++- esphome/components/waveshare_epaper/waveshare_epaper.h | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 42509f39f1..3e1132bb1d 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -46,6 +46,7 @@ WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel" MODELS = { "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), + "1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2), "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), "2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1), @@ -67,7 +68,7 @@ def validate_full_update_every_only_type_a(value): if MODELS[value[CONF_MODEL]][0] != "a": raise cv.Invalid( "The 'full_update_every' option is only available for models " - "'1.54in', '2.13in', '2.90in', and '2.90inV2'." + "'1.54in', '1.54inV2', '2.13in', '2.90in', and '2.90inV2'." ) return value diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 0fb783107d..daaa77b7fa 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -210,6 +210,9 @@ void WaveshareEPaperTypeA::dump_config() { case WAVESHARE_EPAPER_1_54_IN: ESP_LOGCONFIG(TAG, " Model: 1.54in"); break; + case WAVESHARE_EPAPER_1_54_IN_V2: + ESP_LOGCONFIG(TAG, " Model: 1.54inV2"); + break; case WAVESHARE_EPAPER_2_13_IN: ESP_LOGCONFIG(TAG, " Model: 2.13in"); break; @@ -334,7 +337,7 @@ void HOT WaveshareEPaperTypeA::display() { // COMMAND DISPLAY UPDATE CONTROL 2 this->command(0x22); - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) { + if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) { this->data(full_update ? 0xF7 : 0xFF); } else if (this->model_ == TTGO_EPAPER_2_13_IN_B73) { this->data(0xC7); @@ -353,6 +356,8 @@ int WaveshareEPaperTypeA::get_width_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: return 200; + case WAVESHARE_EPAPER_1_54_IN_V2: + return 200; case WAVESHARE_EPAPER_2_13_IN: return 128; case TTGO_EPAPER_2_13_IN: @@ -372,6 +377,8 @@ int WaveshareEPaperTypeA::get_height_internal() { switch (this->model_) { case WAVESHARE_EPAPER_1_54_IN: return 200; + case WAVESHARE_EPAPER_1_54_IN_V2: + return 200; case WAVESHARE_EPAPER_2_13_IN: return 250; case TTGO_EPAPER_2_13_IN: diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 0b8958e7f0..8ab77d653b 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -66,6 +66,7 @@ class WaveshareEPaper : public PollingComponent, enum WaveshareEPaperTypeAModel { WAVESHARE_EPAPER_1_54_IN = 0, + WAVESHARE_EPAPER_1_54_IN_V2, WAVESHARE_EPAPER_2_13_IN, WAVESHARE_EPAPER_2_9_IN, WAVESHARE_EPAPER_2_9_IN_V2, @@ -85,7 +86,7 @@ class WaveshareEPaperTypeA : public WaveshareEPaper { void display() override; void deep_sleep() override { - if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2) { + if (this->model_ == WAVESHARE_EPAPER_2_9_IN_V2 || this->model_ == WAVESHARE_EPAPER_1_54_IN_V2) { // COMMAND DEEP SLEEP MODE this->command(0x10); this->data(0x01); From a8c253a2a5bdf3466f00a42acffcc12f15307f04 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 10 Jun 2021 17:17:52 +1200 Subject: [PATCH 005/105] Bump version to v1.19.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 159a74adc2..30df9abe17 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 4b91cfb7f90f3d34de63428a1df08c30f09ab7a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 11 Jun 2021 23:28:00 +1200 Subject: [PATCH 006/105] Ensure wifi is in at least station mode before starting improv (#1899) --- esphome/components/wifi/wifi_component.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index de7ffb4f09..e98754fac7 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -79,7 +79,8 @@ void WiFiComponent::setup() { } #ifdef USE_IMPROV if (esp32_improv::global_improv_component != nullptr) - esp32_improv::global_improv_component->start(); + if (this->wifi_mode_(true, {})) + esp32_improv::global_improv_component->start(); #endif this->wifi_apply_hostname_(); #if defined(ARDUINO_ARCH_ESP32) && defined(USE_MDNS) @@ -143,11 +144,11 @@ void WiFiComponent::loop() { } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) { - if (!this->is_connected()) { - esp32_improv::global_improv_component->start(); - } - } + if (esp32_improv::global_improv_component != nullptr) + if (!this->is_connected()) + if (this->wifi_mode_(true, {})) + esp32_improv::global_improv_component->start(); + #endif if (!this->has_ap() && this->reboot_timeout_ != 0) { From 575badc6901ad0c960b8080b0ac268e2e450c285 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 08:31:15 +1200 Subject: [PATCH 007/105] Move esp32_ble_server to its own component (#1898) --- CODEOWNERS | 1 + esphome/components/esp32_ble/__init__.py | 26 +--------- esphome/components/esp32_ble/ble.cpp | 11 +++++ esphome/components/esp32_ble/ble.h | 27 +++++++++-- .../components/esp32_ble/ble_advertising.cpp | 4 ++ .../components/esp32_ble/ble_advertising.h | 1 + .../components/esp32_ble_server/__init__.py | 39 +++++++++++++++ .../ble_2901.cpp | 8 ++-- .../ble_2901.h | 4 +- .../ble_2902.cpp | 8 ++-- .../ble_2902.h | 4 +- .../ble_characteristic.cpp | 30 ++++++------ .../ble_characteristic.h | 12 +++-- .../ble_descriptor.cpp | 6 +-- .../ble_descriptor.h | 8 ++-- .../ble_server.cpp | 39 ++++++--------- .../ble_server.h | 23 +++++---- .../ble_service.cpp | 6 +-- .../ble_service.h | 8 ++-- esphome/components/esp32_improv/__init__.py | 11 +++-- .../esp32_improv/esp32_improv_component.cpp | 48 ++++++++----------- .../esp32_improv/esp32_improv_component.h | 30 ++++++------ esphome/core/defines.h | 1 + tests/test5.yaml | 7 +-- 24 files changed, 204 insertions(+), 158 deletions(-) create mode 100644 esphome/components/esp32_ble_server/__init__.py rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2901.cpp (67%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2901.h (80%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2902.cpp (51%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_2902.h (75%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_characteristic.cpp (86%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_characteristic.h (94%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_descriptor.cpp (95%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_descriptor.h (87%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_server.cpp (84%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_server.h (89%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_service.cpp (97%) rename esphome/components/{esp32_ble => esp32_ble_server}/ble_service.h (93%) diff --git a/CODEOWNERS b/CODEOWNERS index 3f1abae5df..0594a60ef6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -36,6 +36,7 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/esp32_ble/* @jesserockz +esphome/components/esp32_ble_server/* @jesserockz esphome/components/esp32_improv/* @jesserockz esphome/components/exposure_notifications/* @OttoWinter esphome/components/ezo/* @ssieb diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index 6437216f69..ccf1f6cafe 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -1,30 +1,18 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CODEOWNERS = ["@jesserockz"] - -CONF_MANUFACTURER = "manufacturer" -CONF_SERVER = "server" +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble") ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component) -BLEServer = esp32_ble_ns.class_("BLEServer", cg.Component) - -BLEServiceComponent = esp32_ble_ns.class_("BLEServiceComponent") CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32BLE), - cv.Optional(CONF_SERVER): cv.Schema( - { - cv.GenerateID(): cv.declare_id(BLEServer), - cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, - cv.Optional(CONF_MODEL): cv.string, - } - ), } ).extend(cv.COMPONENT_SCHEMA) @@ -32,13 +20,3 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - - if CONF_SERVER in config: - conf = config[CONF_SERVER] - server = cg.new_Pvariable(conf[CONF_ID]) - await cg.register_component(server, conf) - cg.add(server.set_manufacturer(conf[CONF_MANUFACTURER])) - if CONF_MODEL in conf: - cg.add(server.set_model(conf[CONF_MODEL])) - cg.add_define("USE_ESP32_BLE_SERVER") - cg.add(var.set_server(server)) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 889c793017..18e80754df 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -1,4 +1,5 @@ #include "ble.h" + #include "esphome/core/application.h" #include "esphome/core/log.h" @@ -26,14 +27,22 @@ void ESP32BLE::setup() { return; } + this->advertising_ = new BLEAdvertising(); + + this->advertising_->set_scan_response(true); + this->advertising_->set_min_preferred_interval(0x06); + this->advertising_->start(); + ESP_LOGD(TAG, "BLE setup complete"); } void ESP32BLE::mark_failed() { Component::mark_failed(); +#ifdef USE_ESP32_BLE_SERVER if (this->server_ != nullptr) { this->server_->mark_failed(); } +#endif } bool ESP32BLE::ble_setup_() { @@ -142,7 +151,9 @@ void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gat void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { ESP_LOGV(TAG, "(BLE) gatts_event [esp_gatt_if: %d] - %d", gatts_if, event); +#ifdef USE_ESP32_BLE_SERVER this->server_->gatts_event_handler(event, gatts_if, param); +#endif } void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, diff --git a/esphome/components/esp32_ble/ble.h b/esphome/components/esp32_ble/ble.h index 27cb9a2092..149f0008a4 100644 --- a/esphome/components/esp32_ble/ble.h +++ b/esphome/components/esp32_ble/ble.h @@ -1,16 +1,21 @@ #pragma once +#include "ble_advertising.h" + #include "esphome/core/component.h" +#include "esphome/core/defines.h" #include "esphome/core/helpers.h" -#include "ble_server.h" #include "queue.h" +#ifdef USE_ESP32_BLE_SERVER +#include "esphome/components/esp32_ble_server/ble_server.h" +#endif + #ifdef ARDUINO_ARCH_ESP32 #include #include #include - namespace esphome { namespace esp32_ble { @@ -28,11 +33,20 @@ class ESP32BLE : public Component { float get_setup_priority() const override; void mark_failed() override; - bool has_server() { return this->server_ != nullptr; } + bool has_server() { +#ifdef USE_ESP32_BLE_SERVER + return this->server_ != nullptr; +#else + return false; +#endif + } bool has_client() { return false; } - void set_server(BLEServer *server) { this->server_ = server; } + BLEAdvertising *get_advertising() { return this->advertising_; } +#ifdef USE_ESP32_BLE_SERVER + void set_server(esp32_ble_server::BLEServer *server) { this->server_ = server; } +#endif protected: static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); @@ -44,8 +58,11 @@ class ESP32BLE : public Component { bool ble_setup_(); - BLEServer *server_{nullptr}; +#ifdef USE_ESP32_BLE_SERVER + esp32_ble_server::BLEServer *server_{nullptr}; +#endif Queue ble_events_; + BLEAdvertising *advertising_; }; extern ESP32BLE *global_ble; diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 6162bf3a40..f215fb48a3 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -32,6 +32,10 @@ BLEAdvertising::BLEAdvertising() { } void BLEAdvertising::add_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.push_back(uuid); } +void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { + this->advertising_uuids_.erase(std::remove(this->advertising_uuids_.begin(), this->advertising_uuids_.end(), uuid), + this->advertising_uuids_.end()); +} void BLEAdvertising::start() { int num_services = this->advertising_uuids_.size(); diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 405edbf482..d86089f333 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -17,6 +17,7 @@ class BLEAdvertising { BLEAdvertising(); void add_service_uuid(ESPBTUUID uuid); + void remove_service_uuid(ESPBTUUID uuid); void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } diff --git a/esphome/components/esp32_ble_server/__init__.py b/esphome/components/esp32_ble_server/__init__.py new file mode 100644 index 0000000000..0dcbcadc50 --- /dev/null +++ b/esphome/components/esp32_ble_server/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_MODEL, ESP_PLATFORM_ESP32 +from esphome.components import esp32_ble + +AUTO_LOAD = ["esp32_ble"] +CODEOWNERS = ["@jesserockz"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +CONF_MANUFACTURER = "manufacturer" +CONF_BLE_ID = "ble_id" + +esp32_ble_server_ns = cg.esphome_ns.namespace("esp32_ble_server") +BLEServer = esp32_ble_server_ns.class_("BLEServer", cg.Component) +BLEServiceComponent = esp32_ble_server_ns.class_("BLEServiceComponent") + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(BLEServer), + cv.GenerateID(CONF_BLE_ID): cv.use_id(esp32_ble.ESP32BLE), + cv.Optional(CONF_MANUFACTURER, default="ESPHome"): cv.string, + cv.Optional(CONF_MODEL): cv.string, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_BLE_ID]) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + cg.add(var.set_manufacturer(config[CONF_MANUFACTURER])) + if CONF_MODEL in config: + cg.add(var.set_model(config[CONF_MODEL])) + cg.add_define("USE_ESP32_BLE_SERVER") + + cg.add(parent.set_server(var)) diff --git a/esphome/components/esp32_ble/ble_2901.cpp b/esphome/components/esp32_ble_server/ble_2901.cpp similarity index 67% rename from esphome/components/esp32_ble/ble_2901.cpp rename to esphome/components/esp32_ble_server/ble_2901.cpp index 952d82d5f0..962ba3ffbe 100644 --- a/esphome/components/esp32_ble/ble_2901.cpp +++ b/esphome/components/esp32_ble_server/ble_2901.cpp @@ -1,18 +1,18 @@ #include "ble_2901.h" -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { BLE2901::BLE2901(const std::string &value) : BLE2901((uint8_t *) value.data(), value.length()) {} -BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(ESPBTUUID::from_uint16(0x2901)) { +BLE2901::BLE2901(const uint8_t *data, size_t length) : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2901)) { this->set_value(data, length); this->permissions_ = ESP_GATT_PERM_READ; } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2901.h b/esphome/components/esp32_ble_server/ble_2901.h similarity index 80% rename from esphome/components/esp32_ble/ble_2901.h rename to esphome/components/esp32_ble_server/ble_2901.h index e606b6271a..3bb23ae69d 100644 --- a/esphome/components/esp32_ble/ble_2901.h +++ b/esphome/components/esp32_ble_server/ble_2901.h @@ -5,7 +5,7 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLE2901 : public BLEDescriptor { public: @@ -13,7 +13,7 @@ class BLE2901 : public BLEDescriptor { BLE2901(const uint8_t *data, size_t length); }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2902.cpp b/esphome/components/esp32_ble_server/ble_2902.cpp similarity index 51% rename from esphome/components/esp32_ble/ble_2902.cpp rename to esphome/components/esp32_ble_server/ble_2902.cpp index 164c8f40ff..0a87b239f9 100644 --- a/esphome/components/esp32_ble/ble_2902.cpp +++ b/esphome/components/esp32_ble_server/ble_2902.cpp @@ -1,18 +1,18 @@ #include "ble_2902.h" -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -BLE2902::BLE2902() : BLEDescriptor(ESPBTUUID::from_uint16(0x2902)) { +BLE2902::BLE2902() : BLEDescriptor(esp32_ble::ESPBTUUID::from_uint16(0x2902)) { this->value_.attr_len = 2; uint8_t data[2] = {0, 0}; memcpy(this->value_.attr_value, data, 2); } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_2902.h b/esphome/components/esp32_ble_server/ble_2902.h similarity index 75% rename from esphome/components/esp32_ble/ble_2902.h rename to esphome/components/esp32_ble_server/ble_2902.h index 356a0bb107..024eec755e 100644 --- a/esphome/components/esp32_ble/ble_2902.h +++ b/esphome/components/esp32_ble_server/ble_2902.h @@ -5,14 +5,14 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLE2902 : public BLEDescriptor { public: BLE2902(); }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp similarity index 86% rename from esphome/components/esp32_ble/ble_characteristic.cpp rename to esphome/components/esp32_ble_server/ble_characteristic.cpp index 127f973146..23b2693839 100644 --- a/esphome/components/esp32_ble/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -7,9 +7,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.characteristic"; +static const char *const TAG = "esp32_ble_server.characteristic"; BLECharacteristic::BLECharacteristic(const ESPBTUUID uuid, uint32_t properties) : uuid_(uuid) { this->set_value_lock_ = xSemaphoreCreateBinary(); @@ -148,39 +148,39 @@ bool BLECharacteristic::is_failed() { void BLECharacteristic::set_broadcast_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } void BLECharacteristic::set_indicate_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } void BLECharacteristic::set_notify_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } void BLECharacteristic::set_read_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); } void BLECharacteristic::set_write_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } void BLECharacteristic::set_write_no_response_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); else - this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, @@ -300,7 +300,7 @@ void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_characteristic.h b/esphome/components/esp32_ble_server/ble_characteristic.h similarity index 94% rename from esphome/components/esp32_ble/ble_characteristic.h rename to esphome/components/esp32_ble_server/ble_characteristic.h index 648cfb939d..bc5033f2ae 100644 --- a/esphome/components/esp32_ble/ble_characteristic.h +++ b/esphome/components/esp32_ble_server/ble_characteristic.h @@ -1,9 +1,9 @@ #pragma once -#include - -#include "ble_uuid.h" #include "ble_descriptor.h" +#include "esphome/components/esp32_ble/ble_uuid.h" + +#include #ifdef ARDUINO_ARCH_ESP32 @@ -14,7 +14,9 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLEService; @@ -89,7 +91,7 @@ class BLECharacteristic { } state_{INIT}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_descriptor.cpp b/esphome/components/esp32_ble_server/ble_descriptor.cpp similarity index 95% rename from esphome/components/esp32_ble/ble_descriptor.cpp rename to esphome/components/esp32_ble_server/ble_descriptor.cpp index 9738cc2fe7..a04343da3a 100644 --- a/esphome/components/esp32_ble/ble_descriptor.cpp +++ b/esphome/components/esp32_ble_server/ble_descriptor.cpp @@ -7,9 +7,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.descriptor"; +static const char *const TAG = "esp32_ble_server.descriptor"; BLEDescriptor::BLEDescriptor(ESPBTUUID uuid, uint16_t max_len) { this->uuid_ = uuid; @@ -71,7 +71,7 @@ void BLEDescriptor::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_ } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_descriptor.h b/esphome/components/esp32_ble_server/ble_descriptor.h similarity index 87% rename from esphome/components/esp32_ble/ble_descriptor.h rename to esphome/components/esp32_ble_server/ble_descriptor.h index bc7b48e331..1a72cb2b54 100644 --- a/esphome/components/esp32_ble/ble_descriptor.h +++ b/esphome/components/esp32_ble_server/ble_descriptor.h @@ -1,6 +1,6 @@ #pragma once -#include "ble_uuid.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 @@ -8,7 +8,9 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLECharacteristic; @@ -43,7 +45,7 @@ class BLEDescriptor { } state_{INIT}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_server.cpp b/esphome/components/esp32_ble_server/ble_server.cpp similarity index 84% rename from esphome/components/esp32_ble/ble_server.cpp rename to esphome/components/esp32_ble_server/ble_server.cpp index 9d5be46113..db83eb6bee 100644 --- a/esphome/components/esp32_ble/ble_server.cpp +++ b/esphome/components/esp32_ble_server/ble_server.cpp @@ -1,5 +1,6 @@ #include "ble_server.h" -#include "ble.h" + +#include "esphome/components/esp32_ble/ble.h" #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/core/version.h" @@ -14,11 +15,11 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.server"; +static const char *const TAG = "esp32_ble_server"; -static const uint16_t device_information_service__UUID = 0x180A; +static const uint16_t DEVICE_INFORMATION_SERVICE_UUID = 0x180A; static const uint16_t MODEL_UUID = 0x2A24; static const uint16_t VERSION_UUID = 0x2A26; static const uint16_t MANUFACTURER_UUID = 0x2A29; @@ -32,8 +33,6 @@ void BLEServer::setup() { ESP_LOGD(TAG, "Setting up BLE Server..."); global_ble_server = this; - - this->advertising_ = new BLEAdvertising(); } void BLEServer::loop() { @@ -53,35 +52,27 @@ void BLEServer::loop() { } case REGISTERING: { if (this->registered_) { - this->device_information_service_ = this->create_service(device_information_service__UUID); + this->device_information_service_ = this->create_service(DEVICE_INFORMATION_SERVICE_UUID); this->create_device_characteristics_(); - this->advertising_->set_scan_response(true); - this->advertising_->set_min_preferred_interval(0x06); - this->advertising_->start(); - this->state_ = STARTING_SERVICE; } break; } case STARTING_SERVICE: { + if (!this->device_information_service_->is_created()) { + break; + } if (this->device_information_service_->is_running()) { - for (auto *component : this->service_components_) { - component->setup_service(); - } - this->state_ = SETTING_UP_COMPONENT_SERVICES; + this->state_ = RUNNING; + this->can_proceed_ = true; + ESP_LOGD(TAG, "BLE server setup successfully"); } else if (!this->device_information_service_->is_starting()) { this->device_information_service_->start(); } break; } - case SETTING_UP_COMPONENT_SERVICES: { - this->state_ = RUNNING; - this->can_proceed_ = true; - ESP_LOGD(TAG, "BLE server setup successfully"); - break; - } } } @@ -123,7 +114,7 @@ BLEService *BLEServer::create_service(ESPBTUUID uuid, bool advertise, uint16_t n BLEService *service = new BLEService(uuid, num_handles, inst_id); this->services_.push_back(service); if (advertise) { - this->advertising_->add_service_uuid(uuid); + esp32_ble::global_ble->get_advertising()->add_service_uuid(uuid); } service->do_create(this); return service; @@ -145,7 +136,7 @@ void BLEServer::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t ga ESP_LOGD(TAG, "BLE Client disconnected"); if (this->remove_client_(param->disconnect.conn_id)) this->connected_clients_--; - this->advertising_->start(); + esp32_ble::global_ble->get_advertising()->start(); for (auto *component : this->service_components_) { component->on_client_disconnect(); } @@ -171,7 +162,7 @@ void BLEServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 BLE Server:"); } BLEServer *global_ble_server = nullptr; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_server.h b/esphome/components/esp32_ble_server/ble_server.h similarity index 89% rename from esphome/components/esp32_ble/ble_server.h rename to esphome/components/esp32_ble_server/ble_server.h index 5657773446..9d955dda79 100644 --- a/esphome/components/esp32_ble/ble_server.h +++ b/esphome/components/esp32_ble_server/ble_server.h @@ -1,15 +1,16 @@ #pragma once +#include "ble_service.h" +#include "ble_characteristic.h" + +#include "esphome/components/esp32_ble/ble_advertising.h" +#include "esphome/components/esp32_ble/ble_uuid.h" +#include "esphome/components/esp32_ble/queue.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "ble_service.h" -#include "ble_characteristic.h" -#include "ble_uuid.h" -#include "ble_advertising.h" -#include -#include "queue.h" +#include #ifdef ARDUINO_ARCH_ESP32 @@ -17,11 +18,12 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { + +using namespace esp32_ble; class BLEServiceComponent { public: - virtual void setup_service(); virtual void on_client_connect(){}; virtual void on_client_disconnect(){}; virtual void start(); @@ -49,7 +51,6 @@ class BLEServer : public Component { esp_gatt_if_t get_gatts_if() { return this->gatts_if_; } uint32_t get_connected_client_count() { return this->connected_clients_; } const std::map &get_clients() { return this->clients_; } - BLEAdvertising *get_advertising() { return this->advertising_; } void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); @@ -69,7 +70,6 @@ class BLEServer : public Component { optional model_; esp_gatt_if_t gatts_if_{0}; bool registered_{false}; - BLEAdvertising *advertising_; uint32_t connected_clients_{0}; std::map clients_; @@ -83,14 +83,13 @@ class BLEServer : public Component { INIT = 0x00, REGISTERING, STARTING_SERVICE, - SETTING_UP_COMPONENT_SERVICES, RUNNING, } state_{INIT}; }; extern BLEServer *global_ble_server; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp similarity index 97% rename from esphome/components/esp32_ble/ble_service.cpp rename to esphome/components/esp32_ble_server/ble_service.cpp index 4b85182696..b7c88a436c 100644 --- a/esphome/components/esp32_ble/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -5,9 +5,9 @@ #ifdef ARDUINO_ARCH_ESP32 namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { -static const char *TAG = "esp32_ble.service"; +static const char *const TAG = "esp32_ble_server.service"; BLEService::BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id) : uuid_(uuid), num_handles_(num_handles), inst_id_(inst_id) {} @@ -136,7 +136,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g } } -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_ble/ble_service.h b/esphome/components/esp32_ble_server/ble_service.h similarity index 93% rename from esphome/components/esp32_ble/ble_service.h rename to esphome/components/esp32_ble_server/ble_service.h index a539631846..9fdb95bde5 100644 --- a/esphome/components/esp32_ble/ble_service.h +++ b/esphome/components/esp32_ble_server/ble_service.h @@ -1,7 +1,7 @@ #pragma once -#include "ble_uuid.h" #include "ble_characteristic.h" +#include "esphome/components/esp32_ble/ble_uuid.h" #ifdef ARDUINO_ARCH_ESP32 @@ -12,10 +12,12 @@ #include namespace esphome { -namespace esp32_ble { +namespace esp32_ble_server { class BLEServer; +using namespace esp32_ble; + class BLEService { public: BLEService(ESPBTUUID uuid, uint16_t num_handles, uint8_t inst_id); @@ -73,7 +75,7 @@ class BLEService { } running_state_{STOPPED}; }; -} // namespace esp32_ble +} // namespace esp32_ble_server } // namespace esphome #endif diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 8b91e9151d..4c337055cb 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -1,12 +1,13 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor, output, esp32_ble +from esphome.components import binary_sensor, output, esp32_ble_server from esphome.const import CONF_ID, ESP_PLATFORM_ESP32 -AUTO_LOAD = ["binary_sensor", "output", "improv"] +AUTO_LOAD = ["binary_sensor", "output", "improv", "esp32_ble_server"] CODEOWNERS = ["@jesserockz"] -DEPENDENCIES = ["esp32_ble", "wifi"] +CONFLICTS_WITH = ["esp32_ble_tracker", "esp32_ble_beacon"] +DEPENDENCIES = ["wifi"] ESP_PLATFORMS = [ESP_PLATFORM_ESP32] CONF_AUTHORIZED_DURATION = "authorized_duration" @@ -18,7 +19,7 @@ CONF_WIFI_TIMEOUT = "wifi_timeout" esp32_improv_ns = cg.esphome_ns.namespace("esp32_improv") ESP32ImprovComponent = esp32_improv_ns.class_( - "ESP32ImprovComponent", cg.Component, esp32_ble.BLEServiceComponent + "ESP32ImprovComponent", cg.Component, esp32_ble_server.BLEServiceComponent ) @@ -33,7 +34,7 @@ def validate_none_(value): CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(ESP32ImprovComponent), - cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble.BLEServer), + cv.GenerateID(CONF_BLE_SERVER_ID): cv.use_id(esp32_ble_server.BLEServer), cv.Required(CONF_AUTHORIZER): cv.Any( validate_none_, cv.use_id(binary_sensor.BinarySensor) ), diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 491f26f46e..e076936771 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -1,7 +1,9 @@ #include "esp32_improv_component.h" -#include "esphome/core/log.h" + +#include "esphome/components/esp32_ble/ble.h" +#include "esphome/components/esp32_ble_server/ble_2902.h" #include "esphome/core/application.h" -#include "esphome/components/esp32_ble/ble_2902.h" +#include "esphome/core/log.h" #ifdef ARDUINO_ARCH_ESP32 @@ -12,40 +14,39 @@ static const char *TAG = "esp32_improv.component"; ESP32ImprovComponent::ESP32ImprovComponent() { global_improv_component = this; } -void ESP32ImprovComponent::setup_service() { - this->service_ = esp32_ble::global_ble_server->create_service(improv::SERVICE_UUID, true); +void ESP32ImprovComponent::setup() { + this->service_ = global_ble_server->create_service(improv::SERVICE_UUID, true); + this->setup_characteristics(); } void ESP32ImprovComponent::setup_characteristics() { this->status_ = this->service_->create_characteristic( - improv::STATUS_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *status_descriptor = new esp32_ble::BLE2902(); + improv::STATUS_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *status_descriptor = new BLE2902(); this->status_->add_descriptor(status_descriptor); this->error_ = this->service_->create_characteristic( - improv::ERROR_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *error_descriptor = new esp32_ble::BLE2902(); + improv::ERROR_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *error_descriptor = new BLE2902(); this->error_->add_descriptor(error_descriptor); - this->rpc_ = - this->service_->create_characteristic(improv::RPC_COMMAND_UUID, esp32_ble::BLECharacteristic::PROPERTY_WRITE); + this->rpc_ = this->service_->create_characteristic(improv::RPC_COMMAND_UUID, BLECharacteristic::PROPERTY_WRITE); this->rpc_->on_write([this](const std::vector &data) { if (data.size() > 0) { this->incoming_data_.insert(this->incoming_data_.end(), data.begin(), data.end()); } }); - esp32_ble::BLEDescriptor *rpc_descriptor = new esp32_ble::BLE2902(); + BLEDescriptor *rpc_descriptor = new BLE2902(); this->rpc_->add_descriptor(rpc_descriptor); - this->rpc_response_ = - this->service_->create_characteristic(improv::RPC_RESULT_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ | - esp32_ble::BLECharacteristic::PROPERTY_NOTIFY); - esp32_ble::BLEDescriptor *rpc_response_descriptor = new esp32_ble::BLE2902(); + this->rpc_response_ = this->service_->create_characteristic( + improv::RPC_RESULT_UUID, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + BLEDescriptor *rpc_response_descriptor = new BLE2902(); this->rpc_response_->add_descriptor(rpc_response_descriptor); this->capabilities_ = - this->service_->create_characteristic(improv::CAPABILITIES_UUID, esp32_ble::BLECharacteristic::PROPERTY_READ); - esp32_ble::BLEDescriptor *capabilities_descriptor = new esp32_ble::BLE2902(); + this->service_->create_characteristic(improv::CAPABILITIES_UUID, BLECharacteristic::PROPERTY_READ); + BLEDescriptor *capabilities_descriptor = new BLE2902(); this->capabilities_->add_descriptor(capabilities_descriptor); uint8_t capabilities = 0x00; if (this->status_indicator_ != nullptr) @@ -64,13 +65,9 @@ void ESP32ImprovComponent::loop() { if (this->status_indicator_ != nullptr) this->status_indicator_->turn_off(); - if (this->service_->is_created() && !this->setup_complete_) { - this->setup_characteristics(); - } - - if (this->should_start_ && this->setup_complete_) { + if (this->service_->is_created() && this->should_start_ && this->setup_complete_) { if (this->service_->is_running()) { - this->service_->get_server()->get_advertising()->start(); + esp32_ble::global_ble->get_advertising()->start(); this->set_state_(improv::STATE_AWAITING_AUTHORIZATION); this->set_error_(improv::ERROR_NONE); @@ -205,10 +202,7 @@ void ESP32ImprovComponent::stop() { }); } -float ESP32ImprovComponent::get_setup_priority() const { - // Before WiFi - return setup_priority::AFTER_BLUETOOTH; -} +float ESP32ImprovComponent::get_setup_priority() const { return setup_priority::AFTER_BLUETOOTH; } void ESP32ImprovComponent::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Improv:"); diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index e9b0c72ecd..262124f983 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -1,26 +1,28 @@ #pragma once +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/esp32_ble_server/ble_server.h" +#include "esphome/components/esp32_ble_server/ble_characteristic.h" +#include "esphome/components/improv/improv.h" +#include "esphome/components/output/binary_output.h" +#include "esphome/components/wifi/wifi_component.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "esphome/components/binary_sensor/binary_sensor.h" -#include "esphome/components/esp32_ble/ble_server.h" -#include "esphome/components/esp32_ble/ble_characteristic.h" -#include "esphome/components/output/binary_output.h" -#include "esphome/components/wifi/wifi_component.h" -#include "esphome/components/improv/improv.h" #ifdef ARDUINO_ARCH_ESP32 namespace esphome { namespace esp32_improv { -class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceComponent { +using namespace esp32_ble_server; + +class ESP32ImprovComponent : public Component, public BLEServiceComponent { public: ESP32ImprovComponent(); void dump_config() override; void loop() override; - void setup_service() override; + void setup() override; void setup_characteristics(); void on_client_disconnect() override; @@ -46,12 +48,12 @@ class ESP32ImprovComponent : public Component, public esp32_ble::BLEServiceCompo std::vector incoming_data_; wifi::WiFiAP connecting_sta_; - esp32_ble::BLEService *service_; - esp32_ble::BLECharacteristic *status_; - esp32_ble::BLECharacteristic *error_; - esp32_ble::BLECharacteristic *rpc_; - esp32_ble::BLECharacteristic *rpc_response_; - esp32_ble::BLECharacteristic *capabilities_; + BLEService *service_; + BLECharacteristic *status_; + BLECharacteristic *error_; + BLECharacteristic *rpc_; + BLECharacteristic *rpc_response_; + BLECharacteristic *capabilities_; binary_sensor::BinarySensor *authorizer_{nullptr}; output::BinaryOutput *status_indicator_{nullptr}; diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 24efd0fd14..90562510b9 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -19,6 +19,7 @@ #define USE_JSON #ifdef ARDUINO_ARCH_ESP32 #define USE_ESP32_CAMERA +#define USE_ESP32_BLE_SERVER #define USE_IMPROV #endif #define USE_TIME diff --git a/tests/test5.yaml b/tests/test5.yaml index d4e19d985b..ba047721e2 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -29,9 +29,10 @@ output: id: built_in_led esp32_ble: - server: - manufacturer: "ESPHome" - model: "Test5" + +esp32_ble_server: + manufacturer: "ESPHome" + model: "Test5" esp32_improv: authorizer: io0_button From 9bb64315f3385cb8fe704349a69e1bfb43c16a98 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 11 Jun 2021 15:49:05 -0700 Subject: [PATCH 008/105] Add new wizard + allow installing firmware over webserial (#1887) --- esphome/dashboard/dashboard.py | 68 +- esphome/dashboard/static/css/esphome.css | 398 ------- .../materialize-stepper.min.css | 10 - .../vendor/materialize/materialize.min.css | 13 - .../static/fonts/material-icons/LICENSE | 202 ---- .../material-icons/MaterialIcons-Regular.woff | Bin 57620 -> 0 bytes .../MaterialIcons-Regular.woff2 | Bin 44300 -> 0 bytes .../static/fonts/material-icons/README.md | 12 - .../fonts/material-icons/material-icons.css | 34 - esphome/dashboard/static/images/favicon.ico | Bin 15086 -> 0 bytes esphome/dashboard/static/js/esphome.js | 1021 ----------------- esphome/dashboard/static/js/vendor/ace/ace.js | 16 - .../static/js/vendor/ace/ext-searchbox.js | 7 - .../static/js/vendor/ace/mode-yaml.js | 7 - .../static/js/vendor/ace/theme-dreamweaver.js | 7 - .../js/vendor/jquery-ui/jquery-ui.min.js | 13 - .../jquery-validate/jquery.validate.min.js | 4 - .../static/js/vendor/jquery/jquery.min.js | 2 - .../materialize-stepper.min.js | 10 - .../js/vendor/materialize/materialize.min.js | 6 - esphome/dashboard/templates/index.html | 678 ----------- esphome/dashboard/templates/login.html | 93 -- esphome/wizard.py | 56 +- requirements.txt | 1 + 24 files changed, 98 insertions(+), 2560 deletions(-) delete mode 100644 esphome/dashboard/static/css/esphome.css delete mode 100644 esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css delete mode 100644 esphome/dashboard/static/css/vendor/materialize/materialize.min.css delete mode 100644 esphome/dashboard/static/fonts/material-icons/LICENSE delete mode 100644 esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff delete mode 100644 esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 delete mode 100644 esphome/dashboard/static/fonts/material-icons/README.md delete mode 100644 esphome/dashboard/static/fonts/material-icons/material-icons.css delete mode 100644 esphome/dashboard/static/images/favicon.ico delete mode 100644 esphome/dashboard/static/js/esphome.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/ace.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/ext-searchbox.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/mode-yaml.js delete mode 100644 esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery-validate/jquery.validate.min.js delete mode 100644 esphome/dashboard/static/js/vendor/jquery/jquery.min.js delete mode 100644 esphome/dashboard/static/js/vendor/materialize-stepper/materialize-stepper.min.js delete mode 100644 esphome/dashboard/static/js/vendor/materialize/materialize.min.js delete mode 100644 esphome/dashboard/templates/index.html delete mode 100644 esphome/dashboard/templates/login.html diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 4b40237768..90061a3d4e 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -9,6 +9,7 @@ import json import logging import multiprocessing import os +import secrets import shutil import subprocess import threading @@ -44,6 +45,8 @@ from esphome.zeroconf import DashboardStatus, Zeroconf _LOGGER = logging.getLogger(__name__) +ENV_DEV = "ESPHOME_DASHBOARD_DEV" + class DashboardSettings: def __init__(self): @@ -111,6 +114,7 @@ def template_args(): docs_link = "https://next.esphome.io/" else: docs_link = "https://www.esphome.io/" + return { "version": version, "docs_link": docs_link, @@ -349,6 +353,7 @@ class SerialPortRequestHandler(BaseHandler): data.append({"port": port.path, "desc": desc}) data.append({"port": "OTA", "desc": "Over-The-Air"}) data.sort(key=lambda x: x["port"], reverse=True) + self.set_header("content-type", "application/json") self.write(json.dumps(data)) @@ -358,11 +363,15 @@ class WizardRequestHandler(BaseHandler): from esphome import wizard kwargs = { - k: "".join(x.decode() for x in v) for k, v in self.request.arguments.items() + k: "".join(x.decode() for x in v) + for k, v in self.request.arguments.items() + if k in ("name", "platform", "board", "ssid", "psk", "password") } + kwargs["ota_password"] = secrets.token_hex(16) destination = settings.rel_path(kwargs["name"] + ".yaml") wizard.wizard_write(path=destination, **kwargs) - self.redirect("./?begin=True") + self.set_status(200) + self.finish() class DownloadBinaryRequestHandler(BaseHandler): @@ -473,7 +482,7 @@ class MainRequestHandler(BaseHandler): entries = _list_dashboard_entries() self.render( - "templates/index.html", + get_template_path("index"), entries=entries, begin=begin, **template_args(), @@ -560,6 +569,7 @@ class PingRequestHandler(BaseHandler): @authenticated def get(self): PING_REQUEST.set() + self.set_header("content-type", "application/json") self.write(json.dumps(PING_RESULT)) @@ -567,6 +577,21 @@ def is_allowed(configuration): return os.path.sep not in configuration +class InfoRequestHandler(BaseHandler): + @authenticated + @bind_config + def get(self, configuration=None): + yaml_path = settings.rel_path(configuration) + all_yaml_files = settings.list_yaml_files() + + if yaml_path not in all_yaml_files: + self.set_status(404) + return + + self.set_header("content-type", "application/json") + self.write(DashboardEntry(yaml_path).storage.to_json()) + + class EditRequestHandler(BaseHandler): @authenticated @bind_config @@ -633,7 +658,7 @@ class LoginHandler(BaseHandler): def render_login_page(self, error=None): self.render( - "templates/login.html", + get_template_path("login"), error=error, hassio=settings.using_hassio_auth, has_username=bool(settings.username), @@ -694,19 +719,44 @@ class LogoutHandler(BaseHandler): _STATIC_FILE_HASHES = {} +def get_base_frontend_path(): + if ENV_DEV not in os.environ: + import esphome_dashboard + + return esphome_dashboard.where() + + static_path = os.environ[ENV_DEV] + if not static_path.endswith("/"): + static_path += "/" + + # This path can be relative, so resolve against the root or else templates don't work + return os.path.abspath(os.path.join(os.getcwd(), static_path, "esphome_dashboard")) + + +def get_template_path(template_name): + return os.path.join(get_base_frontend_path(), f"{template_name}.template.html") + + +def get_static_path(*args): + return os.path.join(get_base_frontend_path(), "static", *args) + + def get_static_file_url(name): - static_path = os.path.join(os.path.dirname(__file__), "static") + # Module imports can't deduplicate if stuff added to url + if name == "js/esphome/index.js": + return f"./static/{name}" + if name in _STATIC_FILE_HASHES: hash_ = _STATIC_FILE_HASHES[name] else: - path = os.path.join(static_path, name) + path = get_static_path(name) with open(path, "rb") as f_handle: hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] _STATIC_FILE_HASHES[name] = hash_ return f"./static/{name}?hash={hash_}" -def make_app(debug=False): +def make_app(debug=get_bool_env(ENV_DEV)): def log_function(handler): if handler.get_status() < 400: log_method = access_log.info @@ -736,7 +786,6 @@ def make_app(debug=False): "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" ) - static_path = os.path.join(os.path.dirname(__file__), "static") app_settings = { "debug": debug, "cookie_secret": settings.cookie_secret, @@ -758,6 +807,7 @@ def make_app(debug=False): (rel + "vscode", EsphomeVscodeHandler), (rel + "ace", EsphomeAceEditorHandler), (rel + "update-all", EsphomeUpdateAllHandler), + (rel + "info", InfoRequestHandler), (rel + "edit", EditRequestHandler), (rel + "download.bin", DownloadBinaryRequestHandler), (rel + "serial-ports", SerialPortRequestHandler), @@ -765,7 +815,7 @@ def make_app(debug=False): (rel + "delete", DeleteRequestHandler), (rel + "undo-delete", UndoDeleteRequestHandler), (rel + "wizard.html", WizardRequestHandler), - (rel + r"static/(.*)", StaticFileHandler, {"path": static_path}), + (rel + r"static/(.*)", StaticFileHandler, {"path": get_static_path()}), ], **app_settings, ) diff --git a/esphome/dashboard/static/css/esphome.css b/esphome/dashboard/static/css/esphome.css deleted file mode 100644 index 116170f95d..0000000000 --- a/esphome/dashboard/static/css/esphome.css +++ /dev/null @@ -1,398 +0,0 @@ -/* Base */ - -:root { - /* Colors */ - --primary-bg-color: #fafafa; - - --alert-standard-color: #666666; - --alert-standard-color-bg: #e6e6e6; - --alert-info-color: #00539f; - --alert-info-color-bg: #E6EEF5; - --alert-success-color: #4CAF50; - --alert-success-color-bg: #EDF7EE; - --alert-warning-color: #FF9800; - --alert-warning-color-bg: #FFF5E6; - --alert-error-color: #D93025; - --alert-error-color-bg: #FAEFEB; -} - -body { - display: flex; - min-height: 100vh; - flex-direction: column; - background-color: var(--primary-bg-color); -} - -/* Layout */ -.valign-wrapper { - position: absolute; - width:100vw; - height:100vh; -} - -.valign { - width: 100%; -} - -main { - flex: 1 0 auto; -} - -/* Alerts & Errors */ -.alert { - width: 100%; - margin: 10px auto; - padding: 10px; - border-radius: 2px; - border-left-width: 4px; - border-left-style: solid; -} - -.alert .title { - font-weight: bold; -} - -.alert .title::after { - content: "\A"; - white-space: pre; -} - -.alert.alert-error { - color: var(--alert-error-color); - border-left-color: var(--alert-error-color); - background-color: var(--alert-error-color-bg); -} - -.card.card-error, .card.status-offline { - border-top: 4px solid var(--alert-error-color); -} - -.card.status-online { - border-top: 4px solid var(--alert-success-color); -} - -.card.status-not-responding { - border-top: 4px solid var(--alert-warning-color); -} - -.card.status-unknown { - border-top: 4px solid var(--alert-standard-color); -} - -/* Login Page */ -#login-page .row.no-bottom-margin { - margin-bottom: 0 !important; -} - -#login-page .logo { - display: block; - width: auto; - max-width: 300px; - margin-left: auto; - margin-right: auto; -} - -#login-page .input-field input:focus + label { - color: #000; -} - -#login-page .input-field input:focus { - border-bottom: 1px solid #000; - box-shadow: 0 1px 0 0 #000; -} - -#login-page .input-field .prefix.active { - color: #000; -} - -#login-page .version-number { - display: block; - text-align: center; - margin-bottom: 20px;; - color:#808080; - font-size: 12px; -} - -#login-page footer { - color: #757575; - font-size: 12px; -} - -#login-page footer a { - color: #424242; -} - -#login-page footer p { - -webkit-margin-before: 0px; - margin-block-start: 0px; - -webkit-margin-after: 5px; - margin-block-end: 5px; -} - -#login-page footer p:last-child { - -webkit-margin-after: 0px; - margin-block-end: 0px; -} - -/* Dashboard */ -.logo-wrapper { - height: 64px; - height: 100%; - width: 0; - margin-left: 24px; -} - -.logo { - width: auto; - height: 48px; - margin: 8px 0; -} - -@media only screen and (max-width: 601px) { - .logo { - height: 38px; - margin: 9px 0; - } -} - -.nav-icons { - margin-right: 24px; -} - -.nav-icons i { - color: black; -} - -nav { - height: auto; - line-height: normal; -} - -.select-port-container { - margin-top: 8px; - margin-right: 10px; - width: 350px; -} - -.serial-port-select { - margin-top: 8px; - margin-right: 10px; - width: 350px; -} - -.serial-port-select .select-dropdown { - color: black; -} - -.serial-port-select .select-dropdown:focus { - border-bottom: 1px solid #607d8b !important; -} - -.serial-port-select .caret { - fill: black; -} - -.serial-port-select .dropdown-content li>span { - color: black; -} - -#nav-dropdown li a, .node-dropdown li a { - color: black; -} - -main .container { - margin-top: 20px; - margin-bottom: 20px; - width: 90%; - max-width: 1920px; -} - -#nodes .card-content { - height: calc(100% - 47px); -} - -#nodes .card-content, #nodes .card-action { - padding: 12px; -} - -#nodes .grid-1-col { - display: grid; - grid-template-columns: 1fr; -} - -#nodes .grid-2-col { - display: grid; - grid-template-columns: 1fr 1fr; - grid-column-gap: 1.5rem; -} - -#nodes .grid-3-col { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - grid-column-gap: 1.5rem; -} - -@media only screen and (max-width: 1100px) { - #nodes .grid-3-col { - grid-template-columns: 1fr 1fr; - grid-column-gap: 1.5rem; - } -} - -@media only screen and (max-width: 750px) { - #nodes .grid-2-col { - grid-template-columns: 1fr; - grid-column-gap: 0; - } - - #nodes .grid-3-col { - grid-template-columns: 1fr; - grid-column-gap: 0; - } -} - -i.node-update-avaliable { - color:#3f51b5; -} - -i.node-webserver { - color:#039be5; -} - -.node-config-path { - margin-top: -8px; - margin-bottom: 8px; - font-size: 14px; -} - -.node-card-comment { - color: #444; - font-style: italic; -} - -.card-action a, .card-dropdown-action a { - cursor: pointer; -} - -.tooltipped { - cursor: help; -} - -#js-loading-indicator { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.editor { - margin-top: 0; - margin-bottom: 0; - border-radius: 3px; - height: calc(100% - 56px); -} - -.inlinecode { - box-sizing: border-box; - padding: 0.2em 0.4em; - margin: 0; - font-size: 85%; - background-color: rgba(27,31,35,0.05); - border-radius: 3px; - font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; -} - -.log { - height: 100%; - max-height: calc(100% - 56px); - background-color: #1c1c1c; - margin-top: 0; - margin-bottom: 0; - font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - padding: 16px; - overflow: auto; - line-height: 1.45; - border-radius: 3px; - white-space: pre-wrap; - overflow-wrap: break-word; - color: #DDD; -} - -.log-bold { font-weight: bold; } -.log-italic { font-style: italic; } -.log-underline { text-decoration: underline; } -.log-strikethrough { text-decoration: line-through; } -.log-underline.log-strikethrough { text-decoration: underline line-through; } -.log-secret { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.log-secret-redacted { - opacity: 0; - width: 1px; - font-size: 1px; -} -.log-fg-black { color: rgb(128,128,128); } -.log-fg-red { color: rgb(255,0,0); } -.log-fg-green { color: rgb(0,255,0); } -.log-fg-yellow { color: rgb(255,255,0); } -.log-fg-blue { color: rgb(0,0,255); } -.log-fg-magenta { color: rgb(255,0,255); } -.log-fg-cyan { color: rgb(0,255,255); } -.log-fg-white { color: rgb(187,187,187); } -.log-bg-black { background-color: rgb(0,0,0); } -.log-bg-red { background-color: rgb(255,0,0); } -.log-bg-green { background-color: rgb(0,255,0); } -.log-bg-yellow { background-color: rgb(255,255,0); } -.log-bg-blue { background-color: rgb(0,0,255); } -.log-bg-magenta { background-color: rgb(255,0,255); } -.log-bg-cyan { background-color: rgb(0,255,255); } -.log-bg-white { background-color: rgb(255,255,255); } - -ul.browser-default { - padding-left: 30px; - margin-top: 10px; - margin-bottom: 15px; -} - -ul.browser-default li { - list-style-type: initial; -} - -ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before { - background-color: #3f51b5 !important; -} - -.select-action { - width: auto !important; - height: auto !important; - white-space: nowrap; -} - -.modal { - width: 95%; - max-height: 90%; - height: 85% !important; -} - -.page-footer { - display: flex; - align-items: center; - min-height: 50px; - padding-top: 0; - color: grey; -} - -.page-footer a { - color: #afafaf; -} - -@media only screen and (max-width: 992px) { - .page-footer .left, .page-footer .right { - width: 100%; - text-align: center; - } -} diff --git a/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css b/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css deleted file mode 100644 index 390f16a011..0000000000 --- a/esphome/dashboard/static/css/vendor/materialize-stepper/materialize-stepper.min.css +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Materialize Stepper - A little plugin that implements a stepper to Materializecss framework. - * @version v3.1.0 - * @author Igor Marcossi (Kinark) . - * @link https://github.com/Kinark/Materialize-stepper - * - * Licensed under the MIT License (https://github.com/Kinark/Materialize-stepper/blob/master/LICENSE). - */ - - .card-content ul.stepper{margin:1em -24px;padding:0 24px}@media only screen and (min-width:993px){.card-content ul.stepper.horizontal{margin-left:-24px;margin-right:-24px;padding-left:24px;padding-right:24px}.card-content ul.stepper.horizontal:first-child{margin-top:-24px}.card-content ul.stepper.horizontal .step.step-content{padding-left:40px;padding-right:40px}.card-content ul.stepper.horizontal .step.step-content .step-actions{padding-left:40px;padding-right:40px}}ul.stepper{counter-reset:section;overflow-y:auto;overflow-x:hidden}ul.stepper .wait-feedback{left:0;right:0;top:0;z-index:2;position:absolute;width:100%;height:100%;text-align:center;display:flex;justify-content:center;align-items:center}ul.stepper .step{position:relative;transition:height .4s cubic-bezier(.4,0,.2,1),padding-bottom .4s cubic-bezier(.4,0,.2,1)}ul.stepper .step .step-title{margin:0 -24px;cursor:pointer;padding:15.5px 44px 24px 64px;display:block}ul.stepper .step .step-title:hover{background-color:rgba(0,0,0,.06)}ul.stepper .step .step-title::after{content:attr(data-step-label);display:block;position:absolute;font-size:12.8px;font-size:.8rem;color:#424242;font-weight:400}ul.stepper .step .step-content{position:relative;display:none;height:0;transition:height .4s cubic-bezier(.4,0,.2,1);width:inherit;overflow:visible;margin-left:41px;margin-right:24px}ul.stepper .step .step-content .step-actions{padding-top:16px;padding-bottom:4px;display:flex;justify-content:flex-start}ul.stepper .step .step-content .step-actions .btn-flat:not(:last-child),ul.stepper .step .step-content .step-actions .btn-large:not(:last-child),ul.stepper .step .step-content .step-actions .btn:not(:last-child){margin-right:5px}ul.stepper .step .step-content .row{margin-bottom:7px}ul.stepper .step::before{position:absolute;counter-increment:section;content:counter(section);height:26px;width:26px;color:#fff;background-color:#b2b2b2;border-radius:50%;text-align:center;line-height:26px;font-weight:400;transition:background-color .4s cubic-bezier(.4,0,.2,1);font-size:14px;left:1px;top:13px}ul.stepper .step.active .step-title{font-weight:500}ul.stepper .step.active .step-content{height:auto;display:block}ul.stepper .step.active::before,ul.stepper .step.done::before{background-color:#2196f3}ul.stepper .step.done::before{content:'\e5ca';font-size:16px;font-family:'Material Icons'}ul.stepper .step.wrong::before{content:'\e001';font-size:24px;font-family:'Material Icons';background-color:red}ul.stepper .step.feedbacking .step-content>:not(.wait-feedback){opacity:.1}ul.stepper .step:not(:last-of-type)::after{content:'';position:absolute;top:52px;left:13.5px;width:1px;height:40%;height:calc(100% - 52px);background-color:rgba(0,0,0,.1);transition:height .4s cubic-bezier(.4,0,.2,1)}ul.stepper .step:not(:last-of-type).active{padding-bottom:36px}ul.stepper>li:not(:last-of-type){padding-bottom:10px}@media only screen and (min-width:993px){ul.stepper.horizontal{position:relative;display:flex;justify-content:space-between;min-height:458px;overflow:hidden}ul.stepper.horizontal::before{content:'';background-color:transparent;width:100%;min-height:84px;box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:absolute;left:0}ul.stepper.horizontal .step{position:static;padding:0!important;width:100%;display:flex;align-items:center;height:84px}ul.stepper.horizontal .step::before{content:none}ul.stepper.horizontal .step:last-of-type{width:auto!important}ul.stepper.horizontal .step.active:not(:last-of-type)::after,ul.stepper.horizontal .step:not(:last-of-type)::after{content:'';position:static;display:inline-block;width:100%;height:1px}ul.stepper.horizontal .step .step-title{line-height:84px;height:84px;margin:0;padding:0 25px 0 65px;display:inline-block;max-width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:0}ul.stepper.horizontal .step .step-title::before{position:absolute;counter-increment:section;content:counter(section);height:26px;width:26px;color:#fff;background-color:#b2b2b2;border-radius:50%;text-align:center;line-height:26px;font-weight:400;transition:background-color .4s cubic-bezier(.4,0,.2,1);font-size:14px;left:1px;top:28.5px;left:19px}ul.stepper.horizontal .step .step-title::after{top:15px}ul.stepper.horizontal .step.active~.step .step-content{left:100%}ul.stepper.horizontal .step.active .step-content{left:0!important}ul.stepper.horizontal .step.active .step-title::before,ul.stepper.horizontal .step.done .step-title::before{background-color:#2196f3}ul.stepper.horizontal .step.done .step-title::before{content:'\e5ca';font-size:16px;font-family:'Material Icons'}ul.stepper.horizontal .step.wrong .step-title::before{content:'\e001';font-size:24px;font-family:'Material Icons';background-color:red}ul.stepper.horizontal .step .step-content{position:absolute;height:calc(100% - 84px);top:84px;display:block;left:-100%;width:100%;overflow-y:auto;overflow-x:hidden;margin:0;padding:20px 20px 76px 20px;transition:left .4s cubic-bezier(.4,0,.2,1)}ul.stepper.horizontal .step .step-content .step-actions{position:absolute;bottom:0;left:0;width:100%;padding:20px;background-color:transparent;flex-direction:row-reverse}ul.stepper.horizontal .step .step-content .step-actions .btn-flat:not(:last-child),ul.stepper.horizontal .step .step-content .step-actions .btn-large:not(:last-child),ul.stepper.horizontal .step .step-content .step-actions .btn:not(:last-child){margin-left:5px;margin-right:0}} diff --git a/esphome/dashboard/static/css/vendor/materialize/materialize.min.css b/esphome/dashboard/static/css/vendor/materialize/materialize.min.css deleted file mode 100644 index 74b1741b62..0000000000 --- a/esphome/dashboard/static/css/vendor/materialize/materialize.min.css +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Materialize v1.0.0 (http://materializecss.com) - * Copyright 2014-2017 Materialize - * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) - */ -.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:rgba(0,0,0,0) !important}.transparent-text{color:rgba(0,0,0,0) !important}/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:0.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace, monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace, monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-cancel-button,[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,*:before,*:after{-webkit-box-sizing:inherit;box-sizing:inherit}button,input,optgroup,select,textarea{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif}ul:not(.browser-default){padding-left:0;list-style-type:none}ul:not(.browser-default)>li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.clearfix{clear:both}.z-depth-0{-webkit-box-shadow:none !important;box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-small,.btn-floating,.dropdown-content,.collapsible,.sidenav{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);box-shadow:0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-small:hover,.btn-floating:hover{-webkit-box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2);box-shadow:0 3px 3px 0 rgba(0,0,0,0.14),0 1px 7px 0 rgba(0,0,0,0.12),0 3px 1px -1px rgba(0,0,0,0.2)}.z-depth-2{-webkit-box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3);box-shadow:0 4px 5px 0 rgba(0,0,0,0.14),0 1px 10px 0 rgba(0,0,0,0.12),0 2px 4px -1px rgba(0,0,0,0.3)}.z-depth-3{-webkit-box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2);box-shadow:0 8px 17px 2px rgba(0,0,0,0.14),0 3px 14px 2px rgba(0,0,0,0.12),0 5px 5px -3px rgba(0,0,0,0.2)}.z-depth-4{-webkit-box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2);box-shadow:0 16px 24px 2px rgba(0,0,0,0.14),0 6px 30px 5px rgba(0,0,0,0.12),0 8px 10px -7px rgba(0,0,0,0.2)}.z-depth-5,.modal{-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2);box-shadow:0 24px 38px 3px rgba(0,0,0,0.14),0 9px 46px 8px rgba(0,0,0,0.12),0 11px 15px -7px rgba(0,0,0,0.2)}.hoverable{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s}.hoverable:hover{-webkit-box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax-container .parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax-container .parallax img{opacity:0;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 1201px){.hide-on-extra-large-only{display:none !important}}@media only screen and (min-width: 1201px){.show-on-extra-large{display:block !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}.page-footer{padding-top:20px;color:#fff;background-color:#ee6e73}.page-footer .footer-copyright{overflow:hidden;min-height:50px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:10px 0px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table;border-collapse:collapse;border-spacing:0}table.striped tr{border-bottom:none}table.striped>tbody>tr:nth-child(odd){background-color:rgba(242,242,242,0.5)}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{-webkit-transition:background-color .25s ease;transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:rgba(242,242,242,0.5)}table.centered thead tr th,table.centered tbody tr td{text-align:center}tr{border-bottom:1px solid rgba(0,0,0,0.12)}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{border-bottom:none;padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid rgba(0,0,0,0.12)}}.collection{margin:.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar:not(.circle-clipper)>.circle,.collection .collection-item.avatar :not(.circle-clipper)>.circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;-webkit-transition:.25s;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;-webkit-transition:width .3s linear;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}span.badge{min-width:3rem;padding:0 6px;margin-left:14px;text-align:center;font-size:1rem;line-height:22px;height:22px;color:#757575;float:right;-webkit-box-sizing:border-box;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{display:inline-block;float:none;margin-left:4px;line-height:22px;height:22px;-webkit-font-smoothing:auto}.collection-item span.badge{margin-top:calc(.75rem - 11px)}.collapsible span.badge{margin-left:auto}.sidenav span.badge{margin-top:calc(24px - 11px)}table span.badge{display:inline-block;float:none;margin-left:auto}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.col .row{margin-left:-.75rem;margin-right:-.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;-webkit-box-sizing:border-box;box-sizing:border-box;padding:0 .75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}@media only screen and (min-width: 1201px){.row .col.xl1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.xl4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.xl7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.xl10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.xl11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.xl12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-xl1{margin-left:8.3333333333%}.row .col.pull-xl1{right:8.3333333333%}.row .col.push-xl1{left:8.3333333333%}.row .col.offset-xl2{margin-left:16.6666666667%}.row .col.pull-xl2{right:16.6666666667%}.row .col.push-xl2{left:16.6666666667%}.row .col.offset-xl3{margin-left:25%}.row .col.pull-xl3{right:25%}.row .col.push-xl3{left:25%}.row .col.offset-xl4{margin-left:33.3333333333%}.row .col.pull-xl4{right:33.3333333333%}.row .col.push-xl4{left:33.3333333333%}.row .col.offset-xl5{margin-left:41.6666666667%}.row .col.pull-xl5{right:41.6666666667%}.row .col.push-xl5{left:41.6666666667%}.row .col.offset-xl6{margin-left:50%}.row .col.pull-xl6{right:50%}.row .col.push-xl6{left:50%}.row .col.offset-xl7{margin-left:58.3333333333%}.row .col.pull-xl7{right:58.3333333333%}.row .col.push-xl7{left:58.3333333333%}.row .col.offset-xl8{margin-left:66.6666666667%}.row .col.pull-xl8{right:66.6666666667%}.row .col.push-xl8{left:66.6666666667%}.row .col.offset-xl9{margin-left:75%}.row .col.pull-xl9{right:75%}.row .col.push-xl9{left:75%}.row .col.offset-xl10{margin-left:83.3333333333%}.row .col.pull-xl10{right:83.3333333333%}.row .col.push-xl10{left:83.3333333333%}.row .col.offset-xl11{margin-left:91.6666666667%}.row .col.pull-xl11{right:91.6666666667%}.row .col.push-xl11{left:91.6666666667%}.row .col.offset-xl12{margin-left:100%}.row .col.pull-xl12{right:100%}.row .col.push-xl12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav.nav-extended{height:auto}nav.nav-extended .nav-wrapper{min-height:56px;height:auto}nav.nav-extended .nav-content{position:relative;line-height:normal}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:24px;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.sidenav-trigger{display:none}}nav .sidenav-trigger{float:left;position:relative;z-index:1;height:56px;margin:0 18px}nav .sidenav-trigger i{height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav .nav-title{display:inline-block;font-size:32px;padding:28px 0}nav ul{margin:0}nav ul li{-webkit-transition:background-color .3s;transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{-webkit-transition:background-color .3s;transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-small,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a.btn>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-small>.material-icons,nav ul a.btn-large>.material-icons,nav ul a.btn-flat>.material-icons,nav ul a.btn-floating>.material-icons{height:inherit;line-height:inherit}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;-webkit-box-shadow:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);-webkit-transition:color .3s;transition:color .3s}nav .input-field label.active i{color:#fff}.navbar-fixed{position:relative;height:56px;z-index:997}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav.nav-extended .nav-wrapper{min-height:64px}nav,nav .nav-wrapper i,nav a.sidenav-trigger,nav a.sidenav-trigger i{height:64px;line-height:64px}.navbar-fixed{height:64px}}a{text-decoration:none}html{line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.3}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.8rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:2.3733333333rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.9466666667rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.52rem 0 .912rem 0}h5{font-size:1.64rem;line-height:110%;margin:1.0933333333rem 0 .656rem 0}h6{font-size:1.15rem;line-height:110%;margin:.7666666667rem 0 .46rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light{font-weight:300}.thin{font-weight:200}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.scale-transition{-webkit-transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:-webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important;transition:transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63), -webkit-transform 0.3s cubic-bezier(0.53, 0.01, 0.36, 1.63) !important}.scale-transition.scale-out{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .2s !important;transition:-webkit-transform .2s !important;transition:transform .2s !important;transition:transform .2s, -webkit-transform .2s !important}.scale-transition.scale-in{-webkit-transform:scale(1);transform:scale(1)}.card-panel{-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;padding:24px;margin:.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:.5rem 0 1rem 0;background-color:#fff;-webkit-transition:-webkit-box-shadow .25s;transition:-webkit-box-shadow .25s;transition:box-shadow .25s;transition:box-shadow .25s, -webkit-box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{border-radius:2px 0 0 2px;max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;max-width:100%;padding:24px}.card .card-content{padding:24px;border-radius:0 0 2px 2px}.card .card-content p{margin:0}.card .card-content .card-title{display:block;line-height:32px;margin-bottom:8px}.card .card-content .card-title i{line-height:32px}.card .card-action{background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);position:relative;padding:16px 24px}.card .card-action:last-child{border-radius:0 0 2px 2px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:24px;-webkit-transition:color .3s ease;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-small):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:24px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;left:0;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:35px;width:auto;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:default}.toast .toast-action{color:#eeff41;font-weight:500;margin-right:-25px;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}.tabs{position:relative;overflow-x:auto;overflow-y:hidden;height:48px;width:100%;background-color:#fff;margin:0 auto;white-space:nowrap}.tabs.tabs-transparent{background-color:transparent}.tabs.tabs-transparent .tab a,.tabs.tabs-transparent .tab.disabled a,.tabs.tabs-transparent .tab.disabled a:hover{color:rgba(255,255,255,0.7)}.tabs.tabs-transparent .tab a:hover,.tabs.tabs-transparent .tab a.active{color:#fff}.tabs.tabs-transparent .indicator{background-color:#fff}.tabs.tabs-fixed-width{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs.tabs-fixed-width .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab{display:inline-block;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase}.tabs .tab a{color:rgba(238,110,115,0.7);display:block;width:100%;height:100%;padding:0 24px;font-size:14px;text-overflow:ellipsis;overflow:hidden;-webkit-transition:color .28s ease, background-color .28s ease;transition:color .28s ease, background-color .28s ease}.tabs .tab a:focus,.tabs .tab a:focus.active{background-color:rgba(246,178,181,0.2);outline:none}.tabs .tab a:hover,.tabs .tab a.active{background-color:transparent;color:#ee6e73}.tabs .tab.disabled a,.tabs .tab.disabled a:hover{color:rgba(238,110,115,0.4);cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}@media only screen and (max-width: 992px){.tabs{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.tabs .tab a{padding:0 12px}}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;visibility:hidden;background-color:#323232}.backdrop{position:absolute;opacity:0;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;visibility:hidden}.btn,.btn-large,.btn-small,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;padding:0 16px;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.disabled.btn-small,.btn-floating.disabled,.btn-large.disabled,.btn-small.disabled,.btn-flat.disabled,.btn:disabled,.btn-large:disabled,.btn-small:disabled,.btn-floating:disabled,.btn-large:disabled,.btn-small:disabled,.btn-flat:disabled,.btn[disabled],.btn-large[disabled],.btn-small[disabled],.btn-floating[disabled],.btn-large[disabled],.btn-small[disabled],.btn-flat[disabled]{pointer-events:none;background-color:#DFDFDF !important;-webkit-box-shadow:none;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled:hover,.disabled.btn-large:hover,.disabled.btn-small:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn-small.disabled:hover,.btn-flat.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-floating:disabled:hover,.btn-large:disabled:hover,.btn-small:disabled:hover,.btn-flat:disabled:hover,.btn[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-floating[disabled]:hover,.btn-large[disabled]:hover,.btn-small[disabled]:hover,.btn-flat[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn,.btn-large,.btn-small,.btn-floating,.btn-large,.btn-small,.btn-flat{font-size:14px;outline:0}.btn i,.btn-large i,.btn-small i,.btn-floating i,.btn-large i,.btn-small i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn:focus,.btn-large:focus,.btn-small:focus,.btn-floating:focus{background-color:#1d7d74}.btn,.btn-large,.btn-small{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;-webkit-transition:background-color .2s ease-out;transition:background-color .2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover,.btn-small:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:40px;height:40px;line-height:40px;padding:0;background-color:#26a69a;border-radius:50%;-webkit-transition:background-color .3s;transition:background-color .3s;cursor:pointer;vertical-align:middle}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:56px;height:56px;padding:0}.btn-floating.btn-large.halfway-fab{bottom:-28px}.btn-floating.btn-large i{line-height:56px}.btn-floating.btn-small{width:32.4px;height:32.4px}.btn-floating.btn-small.halfway-fab{bottom:-16.2px}.btn-floating.btn-small i{line-height:32.4px}.btn-floating.halfway-fab{position:absolute;right:24px;bottom:-20px}.btn-floating.halfway-fab.left{right:auto;left:24px}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:40px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:997}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.direction-left,.fixed-action-btn.direction-right{padding:0 0 0 15px}.fixed-action-btn.direction-left ul,.fixed-action-btn.direction-right ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.direction-left ul li,.fixed-action-btn.direction-right ul li{display:inline-block;margin:7.5px 15px 0 0}.fixed-action-btn.direction-right{padding:0 15px 0 0}.fixed-action-btn.direction-right ul{text-align:left;direction:rtl;left:64px;right:auto}.fixed-action-btn.direction-right ul li{margin:7.5px 0 0 15px}.fixed-action-btn.direction-bottom{padding:0 0 15px 0}.fixed-action-btn.direction-bottom ul{top:64px;bottom:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:reverse;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.fixed-action-btn.direction-bottom ul li{margin:15px 0 0 0}.fixed-action-btn.toolbar{padding:0;height:56px}.fixed-action-btn.toolbar.active>a i{opacity:0}.fixed-action-btn.toolbar ul{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;top:0;bottom:0;z-index:1}.fixed-action-btn.toolbar ul li{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;display:inline-block;margin:0;height:100%;-webkit-transition:none;transition:none}.fixed-action-btn.toolbar ul li a{display:block;overflow:hidden;position:relative;width:100%;height:100%;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:#fff;line-height:56px;z-index:1}.fixed-action-btn.toolbar ul li a i{line-height:inherit}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.fixed-action-btn .fab-backdrop{position:absolute;top:0;left:0;z-index:-1;width:40px;height:40px;background-color:#26a69a;border-radius:50%;-webkit-transform:scale(0);transform:scale(0)}.btn-flat{-webkit-box-shadow:none;box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;-webkit-transition:background-color .2s;transition:background-color .2s}.btn-flat:focus,.btn-flat:hover{-webkit-box-shadow:none;box-shadow:none}.btn-flat:focus{background-color:rgba(0,0,0,0.1)}.btn-flat.disabled,.btn-flat.btn-flat[disabled]{background-color:transparent !important;color:#b3b2b2 !important;cursor:default}.btn-large{height:54px;line-height:54px;font-size:15px;padding:0 28px}.btn-large i{font-size:1.6rem}.btn-small{height:32.4px;line-height:32.4px;font-size:13px}.btn-small i{font-size:1.2rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;overflow-y:auto;opacity:0;position:absolute;left:0;top:0;z-index:9999;-webkit-transform-origin:0 0;transform-origin:0 0}.dropdown-content:focus{outline:0}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left}.dropdown-content li:hover,.dropdown-content li.active{background-color:#eee}.dropdown-content li:focus{outline:none}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:0;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit;float:left;margin:0 24px 0 0;width:24px}body.keyboard-focused .dropdown-content li:focus{background-color:#dadada}.input-field.col .dropdown-content [type="checkbox"]+label{top:1px;left:0;height:18px;-webkit-transform:none;transform:none}.dropdown-trigger{cursor:pointer}/*! - * Waves v0.6.0 - * http://fian.my.id/Waves - * - * Copyright 2014 Alfiana E. Sibuea and other contributors - * Released under the MIT license - * https://github.com/fians/Waves/blob/master/LICENSE - */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;-webkit-transition:.3s ease-out;transition:.3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);-webkit-transition:all 0.7s ease-out;transition:all 0.7s ease-out;-webkit-transition-property:opacity, -webkit-transform;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{-webkit-transition:none !important;transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, white 100%, black 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}.modal:focus{outline:none}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%;text-align:right}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-small,.modal .modal-footer .btn-flat{margin:6px 0}.modal-overlay{position:fixed;z-index:999;top:-25%;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:.5rem 0 1rem 0}.collapsible-header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;cursor:pointer;-webkit-tap-highlight-color:transparent;line-height:1.5;padding:1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header:focus{outline:0}.collapsible-header i{width:2rem;font-size:1.6rem;display:inline-block;text-align:center;margin-right:1rem}.keyboard-focused .collapsible-header:focus{background-color:#eee}.collapsible-body{display:none;border-bottom:1px solid #ddd;-webkit-box-sizing:border-box;box-sizing:border-box;padding:2rem}.sidenav .collapsible,.sidenav.fixed .collapsible{border:none;-webkit-box-shadow:none;box-shadow:none}.sidenav .collapsible li,.sidenav.fixed .collapsible li{padding:0}.sidenav .collapsible-header,.sidenav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.sidenav .collapsible-header:hover,.sidenav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.sidenav .collapsible-header i,.sidenav.fixed .collapsible-header i{line-height:inherit}.sidenav .collapsible-body,.sidenav.fixed .collapsible-body{border:0;background-color:#fff}.sidenav .collapsible-body li a,.sidenav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;-webkit-box-shadow:none;box-shadow:none}.collapsible.popout>li{-webkit-box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;-webkit-transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94);transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{-webkit-box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip:focus{outline:none;background-color:#26a69a;color:#fff}.chip>img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;-webkit-box-shadow:none;box-shadow:none;margin:0 0 8px 0;min-height:45px;outline:none;-webkit-transition:all .3s;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:16px;height:3rem;line-height:32px;outline:0;margin:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;-webkit-box-shadow:none !important;box-shadow:none !important}.chips .autocomplete-content{margin-top:0;margin-bottom:0}.prefix ~ .chips{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.chips:empty ~ label{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;-webkit-transition:opacity .4s;transition:opacity .4s;-webkit-backface-visibility:hidden}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;left:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}::-ms-input-placeholder{color:#d1d1d1}::placeholder{color:#d1d1d1}input:not([type]),input[type=text]:not(.browser-default),input[type=password]:not(.browser-default),input[type=email]:not(.browser-default),input[type=url]:not(.browser-default),input[type=time]:not(.browser-default),input[type=date]:not(.browser-default),input[type=datetime]:not(.browser-default),input[type=datetime-local]:not(.browser-default),input[type=tel]:not(.browser-default),input[type=number]:not(.browser-default),input[type=search]:not(.browser-default),textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;-webkit-box-shadow:none;box-shadow:none;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:border .3s, -webkit-box-shadow .3s;transition:border .3s, -webkit-box-shadow .3s;transition:box-shadow .3s, border .3s;transition:box-shadow .3s, border .3s, -webkit-box-shadow .3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:not(.browser-default):disabled,input[type=text]:not(.browser-default)[readonly="readonly"],input[type=password]:not(.browser-default):disabled,input[type=password]:not(.browser-default)[readonly="readonly"],input[type=email]:not(.browser-default):disabled,input[type=email]:not(.browser-default)[readonly="readonly"],input[type=url]:not(.browser-default):disabled,input[type=url]:not(.browser-default)[readonly="readonly"],input[type=time]:not(.browser-default):disabled,input[type=time]:not(.browser-default)[readonly="readonly"],input[type=date]:not(.browser-default):disabled,input[type=date]:not(.browser-default)[readonly="readonly"],input[type=datetime]:not(.browser-default):disabled,input[type=datetime]:not(.browser-default)[readonly="readonly"],input[type=datetime-local]:not(.browser-default):disabled,input[type=datetime-local]:not(.browser-default)[readonly="readonly"],input[type=tel]:not(.browser-default):disabled,input[type=tel]:not(.browser-default)[readonly="readonly"],input[type=number]:not(.browser-default):disabled,input[type=number]:not(.browser-default)[readonly="readonly"],input[type=search]:not(.browser-default):disabled,input[type=search]:not(.browser-default)[readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.42);border-bottom:1px dotted rgba(0,0,0,0.42)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:not(.browser-default):disabled+label,input[type=text]:not(.browser-default)[readonly="readonly"]+label,input[type=password]:not(.browser-default):disabled+label,input[type=password]:not(.browser-default)[readonly="readonly"]+label,input[type=email]:not(.browser-default):disabled+label,input[type=email]:not(.browser-default)[readonly="readonly"]+label,input[type=url]:not(.browser-default):disabled+label,input[type=url]:not(.browser-default)[readonly="readonly"]+label,input[type=time]:not(.browser-default):disabled+label,input[type=time]:not(.browser-default)[readonly="readonly"]+label,input[type=date]:not(.browser-default):disabled+label,input[type=date]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime]:not(.browser-default):disabled+label,input[type=datetime]:not(.browser-default)[readonly="readonly"]+label,input[type=datetime-local]:not(.browser-default):disabled+label,input[type=datetime-local]:not(.browser-default)[readonly="readonly"]+label,input[type=tel]:not(.browser-default):disabled+label,input[type=tel]:not(.browser-default)[readonly="readonly"]+label,input[type=number]:not(.browser-default):disabled+label,input[type=number]:not(.browser-default)[readonly="readonly"]+label,input[type=search]:not(.browser-default):disabled+label,input[type=search]:not(.browser-default)[readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.42)}input:not([type]):focus:not([readonly]),input[type=text]:not(.browser-default):focus:not([readonly]),input[type=password]:not(.browser-default):focus:not([readonly]),input[type=email]:not(.browser-default):focus:not([readonly]),input[type=url]:not(.browser-default):focus:not([readonly]),input[type=time]:not(.browser-default):focus:not([readonly]),input[type=date]:not(.browser-default):focus:not([readonly]),input[type=datetime]:not(.browser-default):focus:not([readonly]),input[type=datetime-local]:not(.browser-default):focus:not([readonly]),input[type=tel]:not(.browser-default):focus:not([readonly]),input[type=number]:not(.browser-default):focus:not([readonly]),input[type=search]:not(.browser-default):focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;-webkit-box-shadow:0 1px 0 0 #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:not(.browser-default):focus:not([readonly])+label,input[type=password]:not(.browser-default):focus:not([readonly])+label,input[type=email]:not(.browser-default):focus:not([readonly])+label,input[type=url]:not(.browser-default):focus:not([readonly])+label,input[type=time]:not(.browser-default):focus:not([readonly])+label,input[type=date]:not(.browser-default):focus:not([readonly])+label,input[type=datetime]:not(.browser-default):focus:not([readonly])+label,input[type=datetime-local]:not(.browser-default):focus:not([readonly])+label,input[type=tel]:not(.browser-default):focus:not([readonly])+label,input[type=number]:not(.browser-default):focus:not([readonly])+label,input[type=search]:not(.browser-default):focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]):focus.valid ~ label,input[type=text]:not(.browser-default):focus.valid ~ label,input[type=password]:not(.browser-default):focus.valid ~ label,input[type=email]:not(.browser-default):focus.valid ~ label,input[type=url]:not(.browser-default):focus.valid ~ label,input[type=time]:not(.browser-default):focus.valid ~ label,input[type=date]:not(.browser-default):focus.valid ~ label,input[type=datetime]:not(.browser-default):focus.valid ~ label,input[type=datetime-local]:not(.browser-default):focus.valid ~ label,input[type=tel]:not(.browser-default):focus.valid ~ label,input[type=number]:not(.browser-default):focus.valid ~ label,input[type=search]:not(.browser-default):focus.valid ~ label,textarea.materialize-textarea:focus.valid ~ label{color:#4CAF50}input:not([type]):focus.invalid ~ label,input[type=text]:not(.browser-default):focus.invalid ~ label,input[type=password]:not(.browser-default):focus.invalid ~ label,input[type=email]:not(.browser-default):focus.invalid ~ label,input[type=url]:not(.browser-default):focus.invalid ~ label,input[type=time]:not(.browser-default):focus.invalid ~ label,input[type=date]:not(.browser-default):focus.invalid ~ label,input[type=datetime]:not(.browser-default):focus.invalid ~ label,input[type=datetime-local]:not(.browser-default):focus.invalid ~ label,input[type=tel]:not(.browser-default):focus.invalid ~ label,input[type=number]:not(.browser-default):focus.invalid ~ label,input[type=search]:not(.browser-default):focus.invalid ~ label,textarea.materialize-textarea:focus.invalid ~ label{color:#F44336}input:not([type]).validate+label,input[type=text]:not(.browser-default).validate+label,input[type=password]:not(.browser-default).validate+label,input[type=email]:not(.browser-default).validate+label,input[type=url]:not(.browser-default).validate+label,input[type=time]:not(.browser-default).validate+label,input[type=date]:not(.browser-default).validate+label,input[type=datetime]:not(.browser-default).validate+label,input[type=datetime-local]:not(.browser-default).validate+label,input[type=tel]:not(.browser-default).validate+label,input[type=number]:not(.browser-default).validate+label,input[type=search]:not(.browser-default).validate+label,textarea.materialize-textarea.validate+label{width:100%}input.valid:not([type]),input.valid:not([type]):focus,input.valid[type=text]:not(.browser-default),input.valid[type=text]:not(.browser-default):focus,input.valid[type=password]:not(.browser-default),input.valid[type=password]:not(.browser-default):focus,input.valid[type=email]:not(.browser-default),input.valid[type=email]:not(.browser-default):focus,input.valid[type=url]:not(.browser-default),input.valid[type=url]:not(.browser-default):focus,input.valid[type=time]:not(.browser-default),input.valid[type=time]:not(.browser-default):focus,input.valid[type=date]:not(.browser-default),input.valid[type=date]:not(.browser-default):focus,input.valid[type=datetime]:not(.browser-default),input.valid[type=datetime]:not(.browser-default):focus,input.valid[type=datetime-local]:not(.browser-default),input.valid[type=datetime-local]:not(.browser-default):focus,input.valid[type=tel]:not(.browser-default),input.valid[type=tel]:not(.browser-default):focus,input.valid[type=number]:not(.browser-default),input.valid[type=number]:not(.browser-default):focus,input.valid[type=search]:not(.browser-default),input.valid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.valid,textarea.materialize-textarea.valid:focus,.select-wrapper.valid>input.select-dropdown{border-bottom:1px solid #4CAF50;-webkit-box-shadow:0 1px 0 0 #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input.invalid:not([type]),input.invalid:not([type]):focus,input.invalid[type=text]:not(.browser-default),input.invalid[type=text]:not(.browser-default):focus,input.invalid[type=password]:not(.browser-default),input.invalid[type=password]:not(.browser-default):focus,input.invalid[type=email]:not(.browser-default),input.invalid[type=email]:not(.browser-default):focus,input.invalid[type=url]:not(.browser-default),input.invalid[type=url]:not(.browser-default):focus,input.invalid[type=time]:not(.browser-default),input.invalid[type=time]:not(.browser-default):focus,input.invalid[type=date]:not(.browser-default),input.invalid[type=date]:not(.browser-default):focus,input.invalid[type=datetime]:not(.browser-default),input.invalid[type=datetime]:not(.browser-default):focus,input.invalid[type=datetime-local]:not(.browser-default),input.invalid[type=datetime-local]:not(.browser-default):focus,input.invalid[type=tel]:not(.browser-default),input.invalid[type=tel]:not(.browser-default):focus,input.invalid[type=number]:not(.browser-default),input.invalid[type=number]:not(.browser-default):focus,input.invalid[type=search]:not(.browser-default),input.invalid[type=search]:not(.browser-default):focus,textarea.materialize-textarea.invalid,textarea.materialize-textarea.invalid:focus,.select-wrapper.invalid>input.select-dropdown,.select-wrapper.invalid>input.select-dropdown:focus{border-bottom:1px solid #F44336;-webkit-box-shadow:0 1px 0 0 #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).valid ~ .helper-text[data-success],input:not([type]):focus.valid ~ .helper-text[data-success],input:not([type]).invalid ~ .helper-text[data-error],input:not([type]):focus.invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default).valid ~ .helper-text[data-success],input[type=text]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=text]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=text]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default).valid ~ .helper-text[data-success],input[type=password]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=password]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=password]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default).valid ~ .helper-text[data-success],input[type=email]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=email]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=email]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default).valid ~ .helper-text[data-success],input[type=url]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=url]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=url]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default).valid ~ .helper-text[data-success],input[type=time]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=time]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=time]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default).valid ~ .helper-text[data-success],input[type=date]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=date]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=date]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default).valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default).valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=tel]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default).valid ~ .helper-text[data-success],input[type=number]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=number]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=number]:not(.browser-default):focus.invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default).valid ~ .helper-text[data-success],input[type=search]:not(.browser-default):focus.valid ~ .helper-text[data-success],input[type=search]:not(.browser-default).invalid ~ .helper-text[data-error],input[type=search]:not(.browser-default):focus.invalid ~ .helper-text[data-error],textarea.materialize-textarea.valid ~ .helper-text[data-success],textarea.materialize-textarea:focus.valid ~ .helper-text[data-success],textarea.materialize-textarea.invalid ~ .helper-text[data-error],textarea.materialize-textarea:focus.invalid ~ .helper-text[data-error],.select-wrapper.valid .helper-text[data-success],.select-wrapper.invalid ~ .helper-text[data-error]{color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}input:not([type]).valid ~ .helper-text:after,input:not([type]):focus.valid ~ .helper-text:after,input[type=text]:not(.browser-default).valid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=password]:not(.browser-default).valid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=email]:not(.browser-default).valid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=url]:not(.browser-default).valid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=time]:not(.browser-default).valid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=date]:not(.browser-default).valid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).valid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=tel]:not(.browser-default).valid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=number]:not(.browser-default).valid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.valid ~ .helper-text:after,input[type=search]:not(.browser-default).valid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.valid ~ .helper-text:after,textarea.materialize-textarea.valid ~ .helper-text:after,textarea.materialize-textarea:focus.valid ~ .helper-text:after,.select-wrapper.valid ~ .helper-text:after{content:attr(data-success);color:#4CAF50}input:not([type]).invalid ~ .helper-text:after,input:not([type]):focus.invalid ~ .helper-text:after,input[type=text]:not(.browser-default).invalid ~ .helper-text:after,input[type=text]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=password]:not(.browser-default).invalid ~ .helper-text:after,input[type=password]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=email]:not(.browser-default).invalid ~ .helper-text:after,input[type=email]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=url]:not(.browser-default).invalid ~ .helper-text:after,input[type=url]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=time]:not(.browser-default).invalid ~ .helper-text:after,input[type=time]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=date]:not(.browser-default).invalid ~ .helper-text:after,input[type=date]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default).invalid ~ .helper-text:after,input[type=datetime-local]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=tel]:not(.browser-default).invalid ~ .helper-text:after,input[type=tel]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=number]:not(.browser-default).invalid ~ .helper-text:after,input[type=number]:not(.browser-default):focus.invalid ~ .helper-text:after,input[type=search]:not(.browser-default).invalid ~ .helper-text:after,input[type=search]:not(.browser-default):focus.invalid ~ .helper-text:after,textarea.materialize-textarea.invalid ~ .helper-text:after,textarea.materialize-textarea:focus.invalid ~ .helper-text:after,.select-wrapper.invalid ~ .helper-text:after{content:attr(data-error);color:#F44336}input:not([type])+label:after,input[type=text]:not(.browser-default)+label:after,input[type=password]:not(.browser-default)+label:after,input[type=email]:not(.browser-default)+label:after,input[type=url]:not(.browser-default)+label:after,input[type=time]:not(.browser-default)+label:after,input[type=date]:not(.browser-default)+label:after,input[type=datetime]:not(.browser-default)+label:after,input[type=datetime-local]:not(.browser-default)+label:after,input[type=tel]:not(.browser-default)+label:after,input[type=number]:not(.browser-default)+label:after,input[type=search]:not(.browser-default)+label:after,textarea.materialize-textarea+label:after,.select-wrapper+label:after{display:block;content:"";position:absolute;top:100%;left:0;opacity:0;-webkit-transition:.2s opacity ease-out, .2s color ease-out;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem;margin-bottom:1rem}.input-field.inline{display:inline-block;vertical-align:middle;margin-left:5px}.input-field.inline input,.input-field.inline .select-dropdown{margin-bottom:1rem}.input-field.col label{left:.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field>label{color:#9e9e9e;position:absolute;top:0;left:0;font-size:1rem;cursor:text;-webkit-transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:color .2s ease-out, -webkit-transform .2s ease-out;transition:transform .2s ease-out, color .2s ease-out;transition:transform .2s ease-out, color .2s ease-out, -webkit-transform .2s ease-out;-webkit-transform-origin:0% 100%;transform-origin:0% 100%;text-align:initial;-webkit-transform:translateY(12px);transform:translateY(12px)}.input-field>label:not(.label-icon).active{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field>input[type]:-webkit-autofill:not(.browser-default):not([type="search"])+label,.input-field>input[type=date]:not(.browser-default)+label,.input-field>input[type=time]:not(.browser-default)+label{-webkit-transform:translateY(-14px) scale(0.8);transform:translateY(-14px) scale(0.8);-webkit-transform-origin:0 0;transform-origin:0 0}.input-field .helper-text{position:relative;min-height:18px;display:block;font-size:12px;color:rgba(0,0,0,0.54)}.input-field .helper-text::after{opacity:1;position:absolute;top:0;left:0}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;-webkit-transition:color .2s;transition:color .2s;top:.5rem}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .helper-text,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;-webkit-transition:.3s background-color;transition:.3s background-color}.nav-wrapper .input-field input[type=search]{height:inherit;padding-left:4rem;width:calc(100% - 4rem);border:0;-webkit-box-shadow:none;box-shadow:none}.input-field input[type=search]:focus:not(.browser-default){background-color:#fff;border:0;-webkit-box-shadow:none;box-shadow:none;color:#444}.input-field input[type=search]:focus:not(.browser-default)+label i,.input-field input[type=search]:focus:not(.browser-default) ~ .mdi-navigation-close,.input-field input[type=search]:focus:not(.browser-default) ~ .material-icons{color:#444}.input-field input[type=search]+.label-icon{-webkit-transform:none;transform:none;left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;-webkit-transition:.3s color;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{line-height:normal;overflow-y:hidden;padding:.8rem 0 .8rem 0;resize:none;min-height:3rem;-webkit-box-sizing:border-box;box-sizing:border-box}.hiddendiv{visibility:hidden;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem;position:absolute;top:0;z-index:-1}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}.character-counter{min-height:18px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;opacity:0;pointer-events:none}[type="radio"]:not(:checked)+span,[type="radio"]:checked+span{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-transition:.28s ease;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+span:before,[type="radio"]+span:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;-webkit-transition:.28s ease;transition:.28s ease}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after,[type="radio"]:checked+span:before,[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border-radius:50%}[type="radio"]:not(:checked)+span:before,[type="radio"]:not(:checked)+span:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+span:after{-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+span:before{border:2px solid transparent}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:before,[type="radio"].with-gap:checked+span:after{border:2px solid #26a69a}[type="radio"]:checked+span:after,[type="radio"].with-gap:checked+span:after{background-color:#26a69a}[type="radio"]:checked+span:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+span:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+span:before{-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+span:before{border:2px solid rgba(0,0,0,0.42)}[type="radio"].with-gap:disabled:checked+span:after{border:none;background-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before,[type="radio"]:disabled:checked+span:before{background-color:transparent;border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled+span{color:rgba(0,0,0,0.42)}[type="radio"]:disabled:not(:checked)+span:before{border-color:rgba(0,0,0,0.42)}[type="radio"]:disabled:checked+span:after{background-color:rgba(0,0,0,0.42);border-color:#949494}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;opacity:0;pointer-events:none}[type="checkbox"]+span:not(.lever){position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="checkbox"]+span:not(.lever):before,[type="checkbox"]:not(.filled-in)+span:not(.lever):after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:3px;-webkit-transition:.2s;transition:.2s}[type="checkbox"]:not(.filled-in)+span:not(.lever):after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+span:not(.lever):before{border:none;background-color:rgba(0,0,0,0.42)}[type="checkbox"].tabbed:focus+span:not(.lever):after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;-webkit-box-shadow:0 0 0 10px rgba(0,0,0,0.1);box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+span:not(.lever):before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+span:before{border-right:2px solid rgba(0,0,0,0.42);border-bottom:2px solid rgba(0,0,0,0.42)}[type="checkbox"]:indeterminate+span:not(.lever):before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+span:not(.lever):before{border-right:2px solid rgba(0,0,0,0.42);background-color:transparent}[type="checkbox"].filled-in+span:not(.lever):after{border-radius:2px}[type="checkbox"].filled-in+span:not(.lever):before,[type="checkbox"].filled-in+span:not(.lever):after{content:'';left:0;position:absolute;-webkit-transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+span:not(.lever):after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+span:not(.lever):before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+span:not(.lever):after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+span:not(.lever):after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+span:not(.lever):after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+span:not(.lever):after{border-color:transparent;background-color:#949494}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+span:not(.lever):after{background-color:#949494;border-color:#949494}.switch,.switch *{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:before,.switch label input[type=checkbox]:checked+.lever:after{left:18px}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a}.switch label .lever{content:"";display:inline-block;position:relative;width:36px;height:14px;background-color:rgba(0,0,0,0.38);border-radius:15px;margin-right:10px;-webkit-transition:background 0.3s ease;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:before,.switch label .lever:after{content:"";position:absolute;display:inline-block;width:20px;height:20px;border-radius:50%;left:0;top:-3px;-webkit-transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease, transform .1s ease, -webkit-box-shadow 0.1s ease, -webkit-transform .1s ease}.switch label .lever:before{background-color:rgba(38,166,154,0.15)}.switch label .lever:after{background-color:#F1F1F1;-webkit-box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);box-shadow:0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12)}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::before,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(38,166,154,0.15)}input[type=checkbox]:not(:disabled) ~ .lever:active:before,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::before{-webkit-transform:scale(2.4);transform:scale(2.4);background-color:rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default;background-color:rgba(0,0,0,0.12)}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#949494}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper.valid+label,.select-wrapper.invalid+label{width:100%;pointer-events:none}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:16px;margin:0 0 8px 0;padding:0;display:block;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:1}.select-wrapper input.select-dropdown:focus{border-bottom:1px solid #26a69a}.select-wrapper .caret{position:absolute;right:0;top:0;bottom:0;margin:auto 0;z-index:0;fill:rgba(0,0,0,0.87)}.select-wrapper+label{position:absolute;top:-26px;font-size:.8rem}select:disabled{color:rgba(0,0,0,0.42)}.select-wrapper.disabled+label{color:rgba(0,0,0,0.42)}.select-wrapper.disabled .caret{fill:rgba(0,0,0,0.42)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.42);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}body.keyboard-focused .select-dropdown.dropdown-content li:focus{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li:hover{background-color:rgba(0,0,0,0.08)}.select-dropdown.dropdown-content li.selected{background-color:rgba(0,0,0,0.03)}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large,.file-field .btn-small{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.file-field input[type=file]::-webkit-file-upload-button{display:none}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;top:10px;left:0;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;margin-left:7px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;-webkit-appearance:none;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0}.keyboard-focused input[type=range]:focus:not(.active)::-webkit-slider-thumb{-webkit-box-shadow:0 0 0 10px rgba(38,166,154,0.26);box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-moz-focus-inner{border:0}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}.keyboard-focused input[type=range]:focus:not(.active)::-moz-range-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;-webkit-transition:-webkit-box-shadow .3s;transition:-webkit-box-shadow .3s;transition:box-shadow .3s;transition:box-shadow .3s, -webkit-box-shadow .3s}.keyboard-focused input[type=range]:focus:not(.active)::-ms-thumb{box-shadow:0 0 0 10px rgba(38,166,154,0.26)}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:16px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:15px;border-left:1px solid #ee6e73}.table-of-contents a.active{font-weight:500;padding-left:14px;border-left:2px solid #ee6e73}.sidenav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.sidenav .collapsible{margin:0}.sidenav li{float:none;line-height:48px}.sidenav li.active{background-color:rgba(0,0,0,0.05)}.sidenav li>a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.sidenav li>a:hover{background-color:rgba(0,0,0,0.05)}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-flat,.sidenav li>a.btn-floating{margin:10px 15px}.sidenav li>a.btn,.sidenav li>a.btn-large,.sidenav li>a.btn-small,.sidenav li>a.btn-large,.sidenav li>a.btn-floating{color:#fff}.sidenav li>a.btn-flat{color:#343434}.sidenav li>a.btn:hover,.sidenav li>a.btn-large:hover,.sidenav li>a.btn-small:hover,.sidenav li>a.btn-large:hover{background-color:#2bbbad}.sidenav li>a.btn-floating:hover{background-color:#26a69a}.sidenav li>a>i,.sidenav li>a>[class^="mdi-"],.sidenav li>a li>a>[class*="mdi-"],.sidenav li>a>i.material-icons{float:left;height:48px;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.sidenav .divider{margin:8px 0 0 0}.sidenav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.sidenav .subheader:hover{background-color:transparent}.sidenav .user-view{position:relative;padding:32px 32px 0;margin-bottom:8px}.sidenav .user-view>a{height:auto;padding:0}.sidenav .user-view>a:hover{background-color:transparent}.sidenav .user-view .background{overflow:hidden;position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.sidenav .user-view .circle,.sidenav .user-view .name,.sidenav .user-view .email{display:block}.sidenav .user-view .circle{height:64px;width:64px}.sidenav .user-view .name,.sidenav .user-view .email{font-size:14px;line-height:24px}.sidenav .user-view .name{margin-top:16px;font-weight:500}.sidenav .user-view .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.drag-target.right-aligned{right:0}.sidenav.sidenav-fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.sidenav.sidenav-fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.sidenav.sidenav-fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.sidenav.sidenav-fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.sidenav>a{padding:0 16px}.sidenav .user-view{padding:16px 16px 0}}.sidenav .collapsible-body>ul:not(.collapsible)>li.active,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active{background-color:#ee6e73}.sidenav .collapsible-body>ul:not(.collapsible)>li.active a,.sidenav.sidenav-fixed .collapsible-body>ul:not(.collapsible)>li.active a{color:#fff}.sidenav .collapsible-body{padding:0}.sidenav-overlay{position:fixed;top:0;left:0;right:0;opacity:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;display:none}.preloader-wrapper{display:inline-block;position:relative;width:50px;height:50px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{visibility:hidden;width:200px;height:200px;position:absolute;top:0;left:0}.carousel .carousel-item>img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);-webkit-transition:background-color .3s;transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed{pointer-events:none}.tap-target-wrapper{width:800px;height:800px;position:fixed;z-index:1000;visibility:hidden;-webkit-transition:visibility 0s .3s;transition:visibility 0s .3s}.tap-target-wrapper.open{visibility:visible;-webkit-transition:visibility 0s;transition:visibility 0s}.tap-target-wrapper.open .tap-target{-webkit-transform:scale(1);transform:scale(1);opacity:.95;-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-wrapper.open .tap-target-wave::before{-webkit-transform:scale(1);transform:scale(1)}.tap-target-wrapper.open .tap-target-wave::after{visibility:visible;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;-webkit-transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, visibility 0s 1s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s 1s;transition:opacity .3s, transform .3s, visibility 0s 1s, -webkit-transform .3s}.tap-target{position:absolute;font-size:1rem;border-radius:50%;background-color:#ee6e73;-webkit-box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);box-shadow:0 20px 20px 0 rgba(0,0,0,0.14),0 10px 50px 0 rgba(0,0,0,0.12),0 30px 10px -20px rgba(0,0,0,0.2);width:100%;height:100%;opacity:0;-webkit-transform:scale(0);transform:scale(0);-webkit-transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1);transition:transform 0.3s cubic-bezier(0.42, 0, 0.58, 1),opacity 0.3s cubic-bezier(0.42, 0, 0.58, 1),-webkit-transform 0.3s cubic-bezier(0.42, 0, 0.58, 1)}.tap-target-content{position:relative;display:table-cell}.tap-target-wave{position:absolute;border-radius:50%;z-index:10001}.tap-target-wave::before,.tap-target-wave::after{content:'';display:block;position:absolute;width:100%;height:100%;border-radius:50%;background-color:#ffffff}.tap-target-wave::before{-webkit-transform:scale(0);transform:scale(0);-webkit-transition:-webkit-transform .3s;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s, -webkit-transform .3s}.tap-target-wave::after{visibility:hidden;-webkit-transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, visibility 0s, -webkit-transform .3s;transition:opacity .3s, transform .3s, visibility 0s;transition:opacity .3s, transform .3s, visibility 0s, -webkit-transform .3s;z-index:-1}.tap-target-origin{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);transform:translate(-50%, -50%);z-index:10002;position:absolute !important}.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small),.tap-target-origin:not(.btn):not(.btn-large):not(.btn-small):hover{background:none}@media only screen and (max-width: 600px){.tap-target,.tap-target-wrapper{width:600px;height:600px}}.pulse{overflow:visible;position:relative}.pulse::before{content:'';display:block;position:absolute;width:100%;height:100%;top:0;left:0;background-color:inherit;border-radius:inherit;-webkit-transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, -webkit-transform .3s;transition:opacity .3s, transform .3s;transition:opacity .3s, transform .3s, -webkit-transform .3s;-webkit-animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;animation:pulse-animation 1s cubic-bezier(0.24, 0, 0.38, 1) infinite;z-index:-1}@-webkit-keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}@keyframes pulse-animation{0%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}50%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}100%{opacity:0;-webkit-transform:scale(1.5);transform:scale(1.5)}}.datepicker-modal{max-width:325px;min-width:300px;max-height:none}.datepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.datepicker-controls{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;width:280px;margin:0 auto}.datepicker-controls .selects-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.datepicker-controls .select-wrapper input{border-bottom:none;text-align:center;margin:0}.datepicker-controls .select-wrapper input:focus{border-bottom:none}.datepicker-controls .select-wrapper .caret{display:none}.datepicker-controls .select-year input{width:50px}.datepicker-controls .select-month input{width:70px}.month-prev,.month-next{margin-top:4px;cursor:pointer;background-color:transparent;border:none}.datepicker-date-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;color:#fff;padding:20px 22px;font-weight:500}.datepicker-date-display .year-text{display:block;font-size:1.5rem;line-height:25px;color:rgba(255,255,255,0.7)}.datepicker-date-display .date-text{display:block;font-size:2.8rem;line-height:47px;font-weight:500}.datepicker-calendar-container{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.datepicker-table{width:280px;font-size:1rem;margin:0 auto}.datepicker-table thead{border-bottom:none}.datepicker-table th{padding:10px 5px;text-align:center}.datepicker-table tr{border:none}.datepicker-table abbr{text-decoration:none;color:#999}.datepicker-table td{border-radius:50%;padding:0}.datepicker-table td.is-today{color:#26a69a}.datepicker-table td.is-selected{background-color:#26a69a;color:#fff}.datepicker-table td.is-outside-current-month,.datepicker-table td.is-disabled{color:rgba(0,0,0,0.3);pointer-events:none}.datepicker-day-button{background-color:transparent;border:none;line-height:38px;display:block;width:100%;border-radius:50%;padding:0 5px;cursor:pointer;color:inherit}.datepicker-day-button:focus{background-color:rgba(43,161,150,0.25)}.datepicker-footer{width:280px;margin:0 auto;padding-bottom:5px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.datepicker-cancel,.datepicker-clear,.datepicker-today,.datepicker-done{color:#26a69a;padding:0 1rem}.datepicker-clear{color:#F44336}@media only screen and (min-width: 601px){.datepicker-modal{max-width:625px}.datepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.datepicker-date-display{-webkit-box-flex:0;-webkit-flex:0 1 270px;-ms-flex:0 1 270px;flex:0 1 270px}.datepicker-controls,.datepicker-table,.datepicker-footer{width:320px}.datepicker-day-button{line-height:44px}}.timepicker-modal{max-width:325px;max-height:none}.timepicker-container.modal-content{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding:0}.text-primary{color:#fff}.timepicker-digital-display{-webkit-box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;background-color:#26a69a;padding:10px;font-weight:300}.timepicker-text-container{font-size:4rem;font-weight:bold;text-align:center;color:rgba(255,255,255,0.6);font-weight:400;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-span-hours,.timepicker-span-minutes,.timepicker-span-am-pm div{cursor:pointer}.timepicker-span-hours{margin-right:3px}.timepicker-span-minutes{margin-left:3px}.timepicker-display-am-pm{font-size:1.3rem;position:absolute;right:1rem;bottom:1rem;font-weight:400}.timepicker-analog-display{-webkit-box-flex:2.5;-webkit-flex:2.5 auto;-ms-flex:2.5 auto;flex:2.5 auto}.timepicker-plate{background-color:#eee;border-radius:50%;width:270px;height:270px;overflow:visible;position:relative;margin:auto;margin-top:25px;margin-bottom:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.timepicker-canvas,.timepicker-dial{position:absolute;left:0;right:0;top:0;bottom:0}.timepicker-minutes{visibility:hidden}.timepicker-tick{border-radius:50%;color:rgba(0,0,0,0.87);line-height:40px;text-align:center;width:40px;height:40px;position:absolute;cursor:pointer;font-size:15px}.timepicker-tick.active,.timepicker-tick:hover{background-color:rgba(38,166,154,0.25)}.timepicker-dial{-webkit-transition:opacity 350ms, -webkit-transform 350ms;transition:opacity 350ms, -webkit-transform 350ms;transition:transform 350ms, opacity 350ms;transition:transform 350ms, opacity 350ms, -webkit-transform 350ms}.timepicker-dial-out{opacity:0}.timepicker-dial-out.timepicker-hours{-webkit-transform:scale(1.1, 1.1);transform:scale(1.1, 1.1)}.timepicker-dial-out.timepicker-minutes{-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.timepicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.timepicker-canvas line{stroke:#26a69a;stroke-width:4;stroke-linecap:round}.timepicker-canvas-out{opacity:0.25}.timepicker-canvas-bearing{stroke:none;fill:#26a69a}.timepicker-canvas-bg{stroke:none;fill:#26a69a}.timepicker-footer{margin:0 auto;padding:5px 1rem;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.timepicker-clear{color:#F44336}.timepicker-close{color:#26a69a}.timepicker-clear,.timepicker-close{padding:0 20px}@media only screen and (min-width: 601px){.timepicker-modal{max-width:600px}.timepicker-container.modal-content{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row}.timepicker-text-container{top:32%}.timepicker-display-am-pm{position:relative;right:auto;bottom:auto;text-align:center;margin-top:1.2rem}} diff --git a/esphome/dashboard/static/fonts/material-icons/LICENSE b/esphome/dashboard/static/fonts/material-icons/LICENSE deleted file mode 100644 index 7a4a3ea242..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff b/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff deleted file mode 100644 index b648a3eea2d16b6ce783906d6b7d5f251b9eb56c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57620 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

wF7DI|Kx|V1sBQ9FYs>m5C)C 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 BJ$(QG diff --git a/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 b/esphome/dashboard/static/fonts/material-icons/MaterialIcons-Regular.woff2 deleted file mode 100644 index 9fa211252080046a23b2449dbdced6abc2b0bb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44300 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 QW8}wPUeo^@jR61v08@RD2LJ#7 diff --git a/esphome/dashboard/static/fonts/material-icons/README.md b/esphome/dashboard/static/fonts/material-icons/README.md deleted file mode 100644 index 34d980de08..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/README.md +++ /dev/null @@ -1,12 +0,0 @@ -The recommended way to use the Material Icons font is by linking to the web font hosted on Google Fonts: - -```html - -``` - -Read more in our full usage guide: -http://google.github.io/material-design-icons/#icon-font-for-the-web - -Source: -https://github.com/google/material-design-icons diff --git a/esphome/dashboard/static/fonts/material-icons/material-icons.css b/esphome/dashboard/static/fonts/material-icons/material-icons.css deleted file mode 100644 index 51f2e0a0d1..0000000000 --- a/esphome/dashboard/static/fonts/material-icons/material-icons.css +++ /dev/null @@ -1,34 +0,0 @@ -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url(MaterialIcons-Regular.woff2) format('woff2'), - url(MaterialIcons-Regular.woff) format('woff'); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} diff --git a/esphome/dashboard/static/images/favicon.ico b/esphome/dashboard/static/images/favicon.ico deleted file mode 100644 index 88dcd7e2d1ad8943943707409de2a8eb8089924e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmeHO2XvHG7M_qw4IPx0P_t5`NT?e6;lL4)r58cM(m_Ckh(dsaAOsc^QR*rPNOLVq z0?LvgQVao%aE!VbMPLQXuAzm5natbod-LD%&(BOUV{kpYC-0nZ?tS;Q``-V`ePwL6 z09&A~d2<_Rgl%U7n=RUAvqeN0{_-~4*N~M5W%z_3n{5dqL?I56*hT}CYggYzihqk+ zptuE!Tj2ks1p+Mfpx=|s678-y47aB9CbQ=CYy2gUh@cB}5$zZKMJAN;g5rA%Gv{v< zb8kq`o;?HGv}qH@+)9)vq1&!==gz?~F)<;Og@lA?*~1S%9NMEt53EeuX<3I39m0kU z8zws21`Qfif--ZQ?%lfwF)d|*fq^=m5ndD`Wkw>C)TmLEy$N)!TD7W5NlD3V-MTep(}0=yjw(4h zIk$4<%9M2m_QuA>s;yhM(tiV}3D6a(4gmoHqJL6CLV|+JjfjX~8U2Bb)YMdU@!~~= zcvs->U7Z(<%R7OTdUg^@M zDeD5*kZ)o7GnyyII?*xxQGj{o8I-)!#MAGqjOnat&3@rYIlkzm54e1;Uy%)f_ZxuY zFcbLNgiOpc49i=)cCGsI%P&=0TADg@=8PMsPoGwVRc7{+x5n`#lf2HIJ7=$3w=Uab z20$lsnak(;6Z$QHqC2awALl%)0lZ?(Un|4fukCvENWxqf9_ zcYt>%u*bk&&>g@>z;_iZR;&kFp#Dy$Q}?HZ3m2-sefz4RLx(EPHC9mGuU|iP>eMM6 zpWo4ollCu2f2kAa<4XY7&F6r*2IhhGsZ*y;WzcKBw7=136~;Wj{apwO3evJon>J~+ z*^j(9fAn91vS<^m0yYBc4Qv9PShHr$s-Okx&-Q1(YTC4^)(>M|QCBNMLqm1=#*G{A zh5piinn1s$zzPE^L0P9=z!hKmkF=q$?Z0{R=6k7s6!eS-78{`M^MN;j0`-^v`{}2j zs_E0GtLoLO>-NQ(sVUOuqtdTnn<}m+GH#^)ChAZBc^>6l16~ET0$YIBfo;Ha^dHWj z9{Z2zpPrtsN|h?5brl_vCZF!&hdrX@BS((Z>Pj9a-O2i+|1#u#0f+@S-nnKFPXRp& z8^2OM>tNWDN_0f}e031{h!G>SxHnQj&bz?hfPDaM^ml-hfKT(MtO;EID^;qb zbrxNbhxYTka9q>BWy=<=e-Y;&S-+|y&tbp_16;dkM=S!a`Z|6^7f*Gd?yQet7pr?T zer5h&2tC;@p8y#KXb=4ZcW}{HKL@BVoY(#%{a@O)kn(jf^=5n@+FQq$?*xxO`kUMTF8yoQuC4q3uhHL^ zI-28n(BGH+7j2)nb?fK1v-S8j?6;pi?7z4Xa~aORC}G{4G2AMn(QGxGt$E4*t04Vl z{x1*R*~WAtbMqxjmgITUclGMkQ&CY-PM$r`&TQ7Knc}$u&r+~PYhL6SGm7yU&Um!z zm=5W+jPgc}8fh8NA0#iPVOoUSjWeb(R0PgEE?pAeesz6NhiF94#QC0DvSf(^XC#~# zUkASD+{n2dJkOjqpR1dSf}Tq-qN4V?TV=R9K*bZW9^hYl2)u(!Jd+9oDFcFpK%nK z=klUB(SJMJ2WzL@ur=Eu)3l{amn!Z@z<-URT7qVCuRn3(M9R1q?S&;=!N$(Pc`aqL zfVf8-aL%QB4EPbq0%8F= z?m=1p1)wcJw*znh$AL-!$J@sM&pqA%=sE%y0j8Y|&`H@YUlD&BG-wbC`?8{ujqY=3 z+p=iGa%LI&Pz8+XQX-3wj}L$ySgA#e7F^%8Yu>zhFlB4jta0~W%+;k}b5t;*cc#|=2#+7hudHmNAi_$U#T+mQnU`t`b)i>tz7lIN9`7cVj_S}fcu>mcL2rif3*e7Hh|TZ zu!cz;y~!oqS1R@u^Yg?rAIb^3f_Sq|uGqf)h9a8m4{4u<7|FQr;OM4fxx&*=Udkgc zdlZKG;-o&!v0uFkn>P`OYT*3;6!wFkgRccUdk4a}FKB`L^ygqtJHd0$Hh?_#Ky$&j zg3X+TJHp-IB~P$-Io8h&-0MhP;`Z&^rxq<*#B%0wAGKr04h4Sx%$YN<9yxM^{Hku< zx~1`sqFlLhyYaqZFL}J1Nk!O;$B!RZ*aP1N9|_oycR_kEif%XD z>%=<{o@-x0p62pJ2e!#8z-mCs=YDq5oH=uJ`9}Ta?p)t(@r=bIni z)6kjDM4j2$*{uInW_%mb-`z8#dfkcf{rUAT7sSqt2f{PclxMG_8*J#*az7Lub}@Q0llmJnXmMH<4jMN z%kt$O=g^@;jOI)E(*Ep!XMlyk0^kMA=c7SKTINOWM*bE znm^ya4$68PprcMfn){{y#?-ys*fphK7ojY})w2j0pr2k9(Yools8f9`WlC@_T z^MAA$eDm)=QAQb4Ia2@1ka4Z1GnYSf`0(Kh>!*WvTZa!HRy^Yup7RXPF;h}f6#E?4 zY4UulN1ihJ5#PypunDa5udBqnf3;pX)4mTuX`Ey6*5@|(bevD;wQJXoeJ;Iz{rb0f z-VHtj=XpQ&?%kX7&;_iW7s%u6?gxZ@&bJ6ypBay9E1lHa<#YY!Taz)Cqz^tr-LdA< z{+MsFKjKaH$7dFXu-XaVDAz`;B+^x=zU3 zQ~eR_)$x8r#zZM3FU^>sv_s1oJnhSH@`ejXxY(TKjJOi!50!SH&g4nWJo6H{@YXbx zOFHuYI&)fYJo99jIB&d!7t`Ol1zc04hu_rbPgJTcTrIGLW6+0w3uG;@gR%p+00Q(c7@2jz7UVFV-2F>6O4P$5ZrMFM@B}5V15c|)v8qx=A%%n4!DQW;}UmXW?$_ChX#yxC8u&cYn}fAQKvH6w<#1el_-RCuk=Cc7S`EJgn7j;N#@-y*%FX zb5AC20PMTJz!Q)C-}_wa;T+h1I(c`BIo65xaKiTC+IxrTaql=s5`zvh($60fr2v>xd0|;nm!?KKtym*MV&CIk0nXfzQFdH3#$>_Z?WXMlrrPq}TlickIzv-_M}W z?23trc?*5!Aok*KVx3QfJ&*+ZU>n}(o<=&k2V@p{T#&)}Kna{(lth0FAz(X%;T$6b z`G=y+P_$<-_7Ei*w*Xhz*m0NVDG!c~!knaYoh74_;49}~1ZDw;fpi1pDHAY`In93n DM+@@2 diff --git a/esphome/dashboard/static/js/esphome.js b/esphome/dashboard/static/js/esphome.js deleted file mode 100644 index e861f169df..0000000000 --- a/esphome/dashboard/static/js/esphome.js +++ /dev/null @@ -1,1021 +0,0 @@ -'use strict'; - -// Document Ready -$(document).ready(function () { - M.AutoInit(document.body); - nodeGrid(); - startAceWebsocket(); - fixNavbarHeight(); -}); - -// WebSocket URL Helper -const loc = window.location; -const wsLoc = new URL("./", `${loc.protocol}//${loc.host}${loc.pathname}`); -wsLoc.protocol = 'ws:'; -if (loc.protocol === "https:") { - wsLoc.protocol = 'wss:'; -} -const wsUrl = wsLoc.href; - -/** - * Fix NavBar height - */ -const fixNavbarHeight = () => { - const fixFunc = () => { - const sel = $(".select-wrapper"); - $(".navbar-fixed").css("height", (sel.position().top + sel.outerHeight()) + "px"); - } - $(window).resize(fixFunc); - fixFunc(); -} - -/** - * Dashboard Dynamic Grid - */ -const nodeGrid = () => { - const nodeCount = document.querySelectorAll("#nodes .card").length; - const nodeGrid = document.querySelector("#nodes #grid"); - - if (nodeCount <= 3) { - nodeGrid.classList.add("grid-1-col"); - } else if (nodeCount <= 6) { - nodeGrid.classList.add("grid-2-col"); - } else { - nodeGrid.classList.add("grid-3-col"); - } -} - -/** - * Online/ Offline Status Indication - */ - -let isFetchingPing = false; - -const fetchPing = () => { - if (isFetchingPing) { - return; - } - - isFetchingPing = true; - - fetch(`./ping`, { credentials: "same-origin" }).then(res => res.json()) - .then(response => { - for (let filename in response) { - let node = document.querySelector(`#nodes .card[data-filename="${filename}"]`); - - if (node === null) { - continue; - } - - let status = response[filename]; - let className; - - if (status === null) { - className = 'status-unknown'; - } else if (status === true) { - className = 'status-online'; - node.setAttribute('data-last-connected', Date.now().toString()); - } else if (node.hasAttribute('data-last-connected')) { - const attr = parseInt(node.getAttribute('data-last-connected')); - if (Date.now() - attr <= 5000) { - className = 'status-not-responding'; - } else { - className = 'status-offline'; - } - } else { - className = 'status-offline'; - } - - if (node.classList.contains(className)) { - continue; - } - - node.classList.remove('status-unknown', 'status-online', 'status-offline', 'status-not-responding'); - node.classList.add(className); - } - - isFetchingPing = false; - }); -}; -setInterval(fetchPing, 2000); -fetchPing(); - -/** - * Log Color Parsing - */ - -const initializeColorState = () => { - return { - bold: false, - italic: false, - underline: false, - strikethrough: false, - foregroundColor: false, - backgroundColor: false, - carriageReturn: false, - secret: false, - }; -}; - -const colorReplace = (pre, state, text) => { - const re = /(?:\033|\\033)(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; - let i = 0; - - if (state.carriageReturn) { - if (text !== "\n") { - // don't remove if \r\n - pre.removeChild(pre.lastChild); - } - state.carriageReturn = false; - } - - if (text.includes("\r")) { - state.carriageReturn = true; - } - - const lineSpan = document.createElement("span"); - lineSpan.classList.add("line"); - pre.appendChild(lineSpan); - - const addSpan = (content) => { - if (content === "") - return; - - const span = document.createElement("span"); - if (state.bold) span.classList.add("log-bold"); - if (state.italic) span.classList.add("log-italic"); - if (state.underline) span.classList.add("log-underline"); - if (state.strikethrough) span.classList.add("log-strikethrough"); - if (state.secret) span.classList.add("log-secret"); - if (state.foregroundColor !== null) span.classList.add(`log-fg-${state.foregroundColor}`); - if (state.backgroundColor !== null) span.classList.add(`log-bg-${state.backgroundColor}`); - span.appendChild(document.createTextNode(content)); - lineSpan.appendChild(span); - - if (state.secret) { - const redacted = document.createElement("span"); - redacted.classList.add("log-secret-redacted"); - redacted.appendChild(document.createTextNode("[redacted]")); - lineSpan.appendChild(redacted); - } - }; - - - while (true) { - const match = re.exec(text); - if (match === null) - break; - - const j = match.index; - addSpan(text.substring(i, j)); - i = j + match[0].length; - - if (match[1] === undefined) continue; - - for (const colorCode of match[1].split(";")) { - switch (parseInt(colorCode)) { - case 0: - // reset - state.bold = false; - state.italic = false; - state.underline = false; - state.strikethrough = false; - state.foregroundColor = null; - state.backgroundColor = null; - state.secret = false; - break; - case 1: - state.bold = true; - break; - case 3: - state.italic = true; - break; - case 4: - state.underline = true; - break; - case 5: - state.secret = true; - break; - case 6: - state.secret = false; - break; - case 9: - state.strikethrough = true; - break; - case 22: - state.bold = false; - break; - case 23: - state.italic = false; - break; - case 24: - state.underline = false; - break; - case 29: - state.strikethrough = false; - break; - case 30: - state.foregroundColor = "black"; - break; - case 31: - state.foregroundColor = "red"; - break; - case 32: - state.foregroundColor = "green"; - break; - case 33: - state.foregroundColor = "yellow"; - break; - case 34: - state.foregroundColor = "blue"; - break; - case 35: - state.foregroundColor = "magenta"; - break; - case 36: - state.foregroundColor = "cyan"; - break; - case 37: - state.foregroundColor = "white"; - break; - case 39: - state.foregroundColor = null; - break; - case 41: - state.backgroundColor = "red"; - break; - case 42: - state.backgroundColor = "green"; - break; - case 43: - state.backgroundColor = "yellow"; - break; - case 44: - state.backgroundColor = "blue"; - break; - case 45: - state.backgroundColor = "magenta"; - break; - case 46: - state.backgroundColor = "cyan"; - break; - case 47: - state.backgroundColor = "white"; - break; - case 40: - case 49: - state.backgroundColor = null; - break; - } - } - } - addSpan(text.substring(i)); - if (pre.scrollTop + 56 >= (pre.scrollHeight - pre.offsetHeight)) { - // at bottom - pre.scrollTop = pre.scrollHeight; - } -}; - -/** - * Serial Port Selection - */ - -const portSelect = document.querySelector('.nav-wrapper select'); -let ports = []; - -const fetchSerialPorts = (begin = false) => { - fetch(`./serial-ports`, { credentials: "same-origin" }).then(res => res.json()) - .then(response => { - if (ports.length === response.length) { - let allEqual = true; - for (let i = 0; i < response.length; i++) { - if (ports[i].port !== response[i].port) { - allEqual = false; - break; - } - } - if (allEqual) - return; - } - const hasNewPort = response.length >= ports.length; - - ports = response; - - const inst = M.FormSelect.getInstance(portSelect); - if (inst !== undefined) { - inst.destroy(); - } - - portSelect.innerHTML = ""; - const prevSelected = getUploadPort(); - for (let i = 0; i < response.length; i++) { - const val = response[i]; - if (val.port === prevSelected) { - portSelect.innerHTML += ``; - } else { - portSelect.innerHTML += ``; - } - } - - M.FormSelect.init(portSelect, {}); - if (!begin && hasNewPort) - M.toast({ html: "Discovered new serial port." }); - }); -}; - -const getUploadPort = () => { - const inst = M.FormSelect.getInstance(portSelect); - if (inst === undefined) { - return "OTA"; - } - - inst._setSelectedStates(); - return inst.getSelectedValues()[0]; -}; -setInterval(fetchSerialPorts, 5000); -fetchSerialPorts(true); - -/** - * Log Elements - */ - -// Log Modal Class -class LogModal { - constructor({ - name, - onPrepare = (modalElement, config) => { }, - onProcessExit = (modalElement, code) => { }, - onSocketClose = (modalElement) => { }, - dismissible = true - }) { - this.modalId = `js-${name}-modal`; - this.dataAction = `${name}`; - this.wsUrl = `${wsUrl}${name}`; - this.dismissible = dismissible; - this.activeFilename = null; - - this.modalElement = document.getElementById(this.modalId); - this.nodeFilenameElement = document.querySelector(`#${this.modalId} #js-node-filename`); - this.logElement = document.querySelector(`#${this.modalId} #js-log-area`); - this.onPrepare = onPrepare; - this.onProcessExit = onProcessExit; - this.onSocketClose = onSocketClose; - } - - setup() { - const boundOnPress = this._onPress.bind(this); - document.querySelectorAll(`[data-action="${this.dataAction}"]`).forEach((button) => { - button.addEventListener('click', boundOnPress); - }); - } - - _setupModalInstance() { - this.modalInstance = M.Modal.init(this.modalElement, { - onOpenStart: this._onOpenStart.bind(this), - onCloseStart: this._onCloseStart.bind(this), - dismissible: this.dismissible - }) - } - - _onOpenStart() { - document.addEventListener('keydown', this._boundKeydown); - } - - _onCloseStart() { - document.removeEventListener('keydown', this._boundKeydown); - this.activeSocket.close(); - } - - open(event) { - this._onPress(event); - } - - _onPress(event) { - this.activeFilename = event.target.getAttribute('data-filename'); - - this._setupModalInstance(); - this.nodeFilenameElement.innerHTML = this.activeFilename; - - this.logElement.innerHTML = ""; - const colorLogState = initializeColorState(); - - this.onPrepare(this.modalElement, this.activeFilename); - - let stopped = false; - - this.modalInstance.open(); - - const socket = new WebSocket(this.wsUrl); - this.activeSocket = socket; - socket.addEventListener('message', (event) => { - const data = JSON.parse(event.data); - if (data.event === "line") { - colorReplace(this.logElement, colorLogState, data.data); - } else if (data.event === "exit") { - this.onProcessExit(this.modalElement, data.code); - stopped = true; - } - }); - - socket.addEventListener('open', () => { - const msg = JSON.stringify(this._encodeSpawnMessage(this.activeFilename)); - socket.send(msg); - }); - - socket.addEventListener('close', () => { - if (!stopped) { - this.onSocketClose(this.modalElement); - } - }); - } - - _onKeyDown(event) { - // Close on escape key - if (event.keyCode === 27) { - this.modalInstance.close(); - } - } - - _encodeSpawnMessage(filename) { - return { - type: 'spawn', - configuration: filename, - port: getUploadPort(), - }; - } -} - -// Logs Modal -const logsModal = new LogModal({ - name: "logs", - - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("[data-action='stop-logs']").innerHTML = "Stop"; - }, - - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000 - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000 - }); - } - modalElem.querySelector("data-action='stop-logs'").innerHTML = "Close"; - }, - - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000 - }); - } -}) - -logsModal.setup(); - -// Upload Modal -const uploadModal = new LogModal({ - name: "upload", - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-upload-modal [data-action='download-binary']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='upload']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='edit']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-upload-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000 - }); - - modalElement.querySelector("#js-upload-modal [data-action='download-binary']").classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000 - }); - - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.add('disabled'); - modalElement.querySelector("#js-upload-modal [data-action='upload']").classList.remove('disabled'); - } - modalElement.querySelector("#js-upload-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000 - }); - }, - - dismissible: false -}) - -uploadModal.setup(); - -const downloadAfterUploadButton = document.querySelector("#js-upload-modal [data-action='download-binary']"); -downloadAfterUploadButton.addEventListener('click', () => { - const link = document.createElement("a"); - link.download = name; - link.href = `./download.bin?configuration=${encodeURIComponent(uploadModal.activeFilename)}`; - document.body.appendChild(link); - link.click(); - link.remove(); -}); - -// Validate Modal -const validateModal = new LogModal({ - name: 'validate', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-validate-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-validate-modal [data-action='edit']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-validate-modal [data-action='upload']").setAttribute('data-filename', activeFilename); - modalElement.querySelector("#js-validate-modal [data-action='upload']").classList.add('disabled'); - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: `${validateModal.activeFilename} is valid 👍`, - displayLength: 10000, - }); - modalElement.querySelector("#js-validate-modal [data-action='upload']").classList.remove('disabled'); - } else { - M.toast({ - html: `${validateModal.activeFilename} is invalid 😕`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-validate-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -validateModal.setup(); - -// Compile Modal -const compileModal = new LogModal({ - name: 'compile', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-compile-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-compile-modal [data-action='download-binary']").classList.add('disabled'); - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - modalElement.querySelector("#js-compile-modal [data-action='download-binary']").classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${data.code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-compile-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, - dismissible: false, -}); - -compileModal.setup(); - -const downloadAfterCompileButton = document.querySelector("#js-compile-modal [data-action='download-binary']"); -downloadAfterCompileButton.addEventListener('click', () => { - const link = document.createElement("a"); - link.download = name; - link.href = `./download.bin?configuration=${encodeURIComponent(compileModal.activeFilename)}`; - document.body.appendChild(link); - link.click(); - link.remove(); -}); - -// Clean MQTT Modal -const cleanMqttModal = new LogModal({ - name: 'clean-mqtt', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-clean-mqtt-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-clean-mqtt-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -cleanMqttModal.setup(); - -// Clean Build Files Modal -const cleanModal = new LogModal({ - name: 'clean', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-clean-modal [data-action='stop-logs']").innerHTML = "Stop"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - } else { - M.toast({ - html: `Program failed with code ${code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-clean-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, -}); - -cleanModal.setup(); - -// Update All Modal -const updateAllModal = new LogModal({ - name: 'update-all', - onPrepare: (modalElement, activeFilename) => { - modalElement.querySelector("#js-update-all-modal [data-action='stop-logs']").innerHTML = "Stop"; - modalElement.querySelector("#js-update-all-modal #js-node-filename").style.visibility = "hidden"; - }, - onProcessExit: (modalElement, code) => { - if (code === 0) { - M.toast({ - html: "Program exited successfully", - displayLength: 10000, - }); - downloadButton.classList.remove('disabled'); - } else { - M.toast({ - html: `Program failed with code ${data.code}`, - displayLength: 10000, - }); - } - modalElement.querySelector("#js-update-all-modal [data-action='stop-logs']").innerHTML = "Close"; - }, - onSocketClose: (modalElement) => { - M.toast({ - html: 'Terminated process', - displayLength: 10000, - }); - }, - dismissible: false, -}); - -updateAllModal.setup(); - -/** - * Node Editing - */ - -let editorActiveFilename = null; -let editorActiveSecrets = false; -let editorActiveWebSocket = null; -let editorValidationScheduled = false; -let editorValidationRunning = false; - -// Setup Editor -const editorElement = document.querySelector("#js-editor-modal #js-editor-area"); -const editor = ace.edit(editorElement); - -editor.setOptions({ - highlightActiveLine: true, - showPrintMargin: true, - useSoftTabs: true, - tabSize: 2, - useWorker: false, - theme: 'ace/theme/dreamweaver', - mode: 'ace/mode/yaml' -}); - -editor.commands.addCommand({ - name: 'saveCommand', - bindKey: { win: 'Ctrl-S', mac: 'Command-S' }, - exec: function () { - saveFile(editorActiveFilename); - }, - readOnly: false -}); - -// Edit Button Listener -document.querySelectorAll("[data-action='edit']").forEach((button) => { - button.addEventListener('click', (event) => { - - editorActiveFilename = event.target.getAttribute("data-filename"); - const filenameField = document.querySelector("#js-editor-modal #js-node-filename"); - filenameField.innerHTML = editorActiveFilename; - - const saveButton = document.querySelector("#js-editor-modal [data-action='save']"); - const uploadButton = document.querySelector("#js-editor-modal [data-action='upload']"); - const closeButton = document.querySelector("#js-editor-modal [data-action='close']"); - saveButton.setAttribute('data-filename', editorActiveFilename); - uploadButton.setAttribute('data-filename', editorActiveFilename); - uploadButton.setAttribute('onClick', `saveFile("${editorActiveFilename}")`); - if (editorActiveFilename === "secrets.yaml") { - uploadButton.classList.add("disabled"); - editorActiveSecrets = true; - } else { - uploadButton.classList.remove("disabled"); - editorActiveSecrets = false; - } - closeButton.setAttribute('data-filename', editorActiveFilename); - - const loadingIndicator = document.querySelector("#js-editor-modal #js-loading-indicator"); - const editorArea = document.querySelector("#js-editor-modal #js-editor-area"); - - loadingIndicator.style.display = "block"; - editorArea.style.display = "none"; - - editor.setOption('readOnly', true); - fetch(`./edit?configuration=${editorActiveFilename}`, { credentials: "same-origin" }) - .then(res => res.text()).then(response => { - editor.setValue(response, -1); - editor.setOption('readOnly', false); - loadingIndicator.style.display = "none"; - editorArea.style.display = "block"; - }); - editor.focus(); - - const editModalElement = document.getElementById("js-editor-modal"); - const editorModal = M.Modal.init(editModalElement, { - onOpenStart: function () { - editorModalOnOpen() - }, - onCloseStart: function () { - editorModalOnClose() - }, - dismissible: false - }) - - editorModal.open(); - - }); -}); - -// Editor On Open -const editorModalOnOpen = () => { - return -} - -// Editor On Close -const editorModalOnClose = () => { - editorActiveFilename = null; -} - -// Editor WebSocket Validation -const startAceWebsocket = () => { - editorActiveWebSocket = new WebSocket(`${wsUrl}ace`); - - editorActiveWebSocket.addEventListener('message', (event) => { - const raw = JSON.parse(event.data); - if (raw.event === "line") { - const msg = JSON.parse(raw.data); - if (msg.type === "result") { - const arr = []; - - for (const v of msg.validation_errors) { - let o = { - text: v.message, - type: 'error', - row: 0, - column: 0 - }; - if (v.range != null) { - o.row = v.range.start_line; - o.column = v.range.start_col; - } - arr.push(o); - } - for (const v of msg.yaml_errors) { - arr.push({ - text: v.message, - type: 'error', - row: 0, - column: 0 - }); - } - - editor.session.setAnnotations(arr); - - if (arr.length) { - document.querySelector("#js-editor-modal [data-action='upload']").classList.add('disabled'); - } else { - document.querySelector("#js-editor-modal [data-action='upload']").classList.remove('disabled'); - } - - editorValidationRunning = false; - } else if (msg.type === "read_file") { - sendAceStdin({ - type: 'file_response', - content: editor.getValue() - }); - } - } - }) - - editorActiveWebSocket.addEventListener('open', () => { - const msg = JSON.stringify({ type: 'spawn' }); - editorActiveWebSocket.send(msg); - }); - - editorActiveWebSocket.addEventListener('close', () => { - editorActiveWebSocket = null; - setTimeout(startAceWebsocket, 5000); - }); -}; - -const sendAceStdin = (data) => { - let send = JSON.stringify({ - type: 'stdin', - data: JSON.stringify(data) + '\n', - }); - editorActiveWebSocket.send(send); -}; - -const debounce = (func, wait) => { - let timeout; - return function () { - let context = this, args = arguments; - let later = function () { - timeout = null; - func.apply(context, args); - }; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - }; -}; - -editor.session.on('change', debounce(() => { - editorValidationScheduled = !editorActiveSecrets; -}, 250)); - -setInterval(() => { - if (!editorValidationScheduled || editorValidationRunning) - return; - if (editorActiveWebSocket == null) - return; - - sendAceStdin({ - type: 'validate', - file: editorActiveFilename - }); - editorValidationRunning = true; - editorValidationScheduled = false; -}, 100); - -// Save File -const saveFile = (filename) => { - const extensionRegex = new RegExp("(?:\.([^.]+))?$"); - - if (filename.match(extensionRegex)[0] !== ".yaml") { - M.toast({ - html: `❌ File ${filename} cannot be saved as it is not a YAML file!`, - displayLength: 10000 - }); - return; - } - - fetch(`./edit?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - body: editor.getValue() - }) - .then((response) => { - response.text(); - }) - .then(() => { - M.toast({ - html: `✅ Saved ${filename}`, - displayLength: 10000 - }); - }) - .catch((error) => { - M.toast({ - html: `❌ An error occured saving ${filename}`, - displayLength: 10000 - }); - }) -} - -document.querySelectorAll("[data-action='save']").forEach((btn) => { - btn.addEventListener("click", (e) => { - saveFile(editorActiveFilename); - }); -}); - -// Delete Node -document.querySelectorAll("[data-action='delete']").forEach((btn) => { - btn.addEventListener("click", (e) => { - const filename = e.target.getAttribute("data-filename"); - - fetch(`./delete?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - }) - .then((res) => { - res.text() - }) - .then(() => { - const toastHtml = `🗑️ Deleted ${filename} - `; - const toast = M.toast({ - html: toastHtml, - displayLength: 10000 - }); - const undoButton = toast.el.querySelector('.toast-action'); - - document.querySelector(`.card[data-filename="${filename}"]`).remove(); - - undoButton.addEventListener('click', () => { - fetch(`./undo-delete?configuration=${filename}`, { - credentials: "same-origin", - method: "POST", - }) - .then((res) => { - res.text() - }) - .then(() => { - window.location.reload(false); - }); - }); - }); - - }); -}); - -/** - * Wizard - */ - -const wizardTriggerElement = document.querySelector("[data-action='wizard']"); -const wizardModal = document.getElementById("js-wizard-modal"); -const wizardCloseButton = document.querySelector("[data-action='wizard-close']"); - -const wizardStepper = document.querySelector('#js-wizard-modal .stepper'); -const wizardStepperInstace = new MStepper(wizardStepper, { - firstActive: 0, - stepTitleNavigation: false, - autoFocusInput: true, - showFeedbackLoader: true, -}) - -const startWizard = () => { - M.Modal.init(wizardModal, { - dismissible: false - }).open(); -} - -document.querySelectorAll("[data-action='wizard']").forEach((btn) => { - btn.addEventListener("click", (event) => { - startWizard(); - }) -}); - -jQuery.validator.addMethod("nospaces", (value, element) => { - return value.indexOf(' ') < 0; -}, "Name cannot contain any spaces!"); - -jQuery.validator.addMethod("nounderscores", (value, element) => { - return value.indexOf('_') < 0; -}, "Name cannot contain underscores!"); - -jQuery.validator.addMethod("lowercase", (value, element) => { - return value === value.toLowerCase(); -}, "Name must be all lower case!"); diff --git a/esphome/dashboard/static/js/vendor/ace/ace.js b/esphome/dashboard/static/js/vendor/ace/ace.js deleted file mode 100644 index 0a66043d67..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/ace.js +++ /dev/null @@ -1,16 +0,0 @@ -(function () { function o(n) { var i = e; n && (e[n] || (e[n] = {}), i = e[n]); if (!i.define || !i.define.packaged) t.original = i.define, i.define = t, i.define.packaged = !0; if (!i.require || !i.require.packaged) r.original = i.require, i.require = r, i.require.packaged = !0 } var ACE_NAMESPACE = "", e = function () { return this }(); !e && typeof window != "undefined" && (e = window); if (!ACE_NAMESPACE && typeof requirejs != "undefined") return; var t = function (e, n, r) { if (typeof e != "string") { t.original ? t.original.apply(this, arguments) : (console.error("dropping module because define wasn't a string."), console.trace()); return } arguments.length == 2 && (r = n), t.modules[e] || (t.payloads[e] = r, t.modules[e] = null) }; t.modules = {}, t.payloads = {}; var n = function (e, t, n) { if (typeof t == "string") { var i = s(e, t); if (i != undefined) return n && n(), i } else if (Object.prototype.toString.call(t) === "[object Array]") { var o = []; for (var u = 0, a = t.length; u < a; ++u) { var f = s(e, t[u]); if (f == undefined && r.original) return; o.push(f) } return n && n.apply(null, o) || !0 } }, r = function (e, t) { var i = n("", e, t); return i == undefined && r.original ? r.original.apply(this, arguments) : i }, i = function (e, t) { if (t.indexOf("!") !== -1) { var n = t.split("!"); return i(e, n[0]) + "!" + i(e, n[1]) } if (t.charAt(0) == ".") { var r = e.split("/").slice(0, -1).join("/"); t = r + "/" + t; while (t.indexOf(".") !== -1 && s != t) { var s = t; t = t.replace(/\/\.\//, "/").replace(/[^\/]+\/\.\.\//, "") } } return t }, s = function (e, r) { r = i(e, r); var s = t.modules[r]; if (!s) { s = t.payloads[r]; if (typeof s == "function") { var o = {}, u = { id: r, uri: "", exports: o, packaged: !0 }, a = function (e, t) { return n(r, e, t) }, f = s(a, o, u); o = f || u.exports, t.modules[r] = o, delete t.payloads[r] } s = t.modules[r] = o || s } return s }; o(ACE_NAMESPACE) })(), define("ace/lib/regexp", ["require", "exports", "module"], function (e, t, n) { "use strict"; function o(e) { return (e.global ? "g" : "") + (e.ignoreCase ? "i" : "") + (e.multiline ? "m" : "") + (e.extended ? "x" : "") + (e.sticky ? "y" : "") } function u(e, t, n) { if (Array.prototype.indexOf) return e.indexOf(t, n); for (var r = n || 0; r < e.length; r++)if (e[r] === t) return r; return -1 } var r = { exec: RegExp.prototype.exec, test: RegExp.prototype.test, match: String.prototype.match, replace: String.prototype.replace, split: String.prototype.split }, i = r.exec.call(/()??/, "")[1] === undefined, s = function () { var e = /^/g; return r.test.call(e, ""), !e.lastIndex }(); if (s && i) return; RegExp.prototype.exec = function (e) { var t = r.exec.apply(this, arguments), n, a; if (typeof e == "string" && t) { !i && t.length > 1 && u(t, "") > -1 && (a = RegExp(this.source, r.replace.call(o(this), "g", "")), r.replace.call(e.slice(t.index), a, function () { for (var e = 1; e < arguments.length - 2; e++)arguments[e] === undefined && (t[e] = undefined) })); if (this._xregexp && this._xregexp.captureNames) for (var f = 1; f < t.length; f++)n = this._xregexp.captureNames[f - 1], n && (t[n] = t[f]); !s && this.global && !t[0].length && this.lastIndex > t.index && this.lastIndex-- } return t }, s || (RegExp.prototype.test = function (e) { var t = r.exec.call(this, e); return t && this.global && !t[0].length && this.lastIndex > t.index && this.lastIndex--, !!t }) }), define("ace/lib/es5-shim", ["require", "exports", "module"], function (e, t, n) { function r() { } function w(e) { try { return Object.defineProperty(e, "sentinel", {}), "sentinel" in e } catch (t) { } } function H(e) { return e = +e, e !== e ? e = 0 : e !== 0 && e !== 1 / 0 && e !== -1 / 0 && (e = (e > 0 || -1) * Math.floor(Math.abs(e))), e } function B(e) { var t = typeof e; return e === null || t === "undefined" || t === "boolean" || t === "number" || t === "string" } function j(e) { var t, n, r; if (B(e)) return e; n = e.valueOf; if (typeof n == "function") { t = n.call(e); if (B(t)) return t } r = e.toString; if (typeof r == "function") { t = r.call(e); if (B(t)) return t } throw new TypeError } Function.prototype.bind || (Function.prototype.bind = function (t) { var n = this; if (typeof n != "function") throw new TypeError("Function.prototype.bind called on incompatible " + n); var i = u.call(arguments, 1), s = function () { if (this instanceof s) { var e = n.apply(this, i.concat(u.call(arguments))); return Object(e) === e ? e : this } return n.apply(t, i.concat(u.call(arguments))) }; return n.prototype && (r.prototype = n.prototype, s.prototype = new r, r.prototype = null), s }); var i = Function.prototype.call, s = Array.prototype, o = Object.prototype, u = s.slice, a = i.bind(o.toString), f = i.bind(o.hasOwnProperty), l, c, h, p, d; if (d = f(o, "__defineGetter__")) l = i.bind(o.__defineGetter__), c = i.bind(o.__defineSetter__), h = i.bind(o.__lookupGetter__), p = i.bind(o.__lookupSetter__); if ([1, 2].splice(0).length != 2) if (!function () { function e(e) { var t = new Array(e + 2); return t[0] = t[1] = 0, t } var t = [], n; t.splice.apply(t, e(20)), t.splice.apply(t, e(26)), n = t.length, t.splice(5, 0, "XXX"), n + 1 == t.length; if (n + 1 == t.length) return !0 }()) Array.prototype.splice = function (e, t) { var n = this.length; e > 0 ? e > n && (e = n) : e == void 0 ? e = 0 : e < 0 && (e = Math.max(n + e, 0)), e + t < n || (t = n - e); var r = this.slice(e, e + t), i = u.call(arguments, 2), s = i.length; if (e === n) s && this.push.apply(this, i); else { var o = Math.min(t, n - e), a = e + o, f = a + s - o, l = n - a, c = n - o; if (f < a) for (var h = 0; h < l; ++h)this[f + h] = this[a + h]; else if (f > a) for (h = l; h--;)this[f + h] = this[a + h]; if (s && e === c) this.length = c, this.push.apply(this, i); else { this.length = c + s; for (h = 0; h < s; ++h)this[e + h] = i[h] } } return r }; else { var v = Array.prototype.splice; Array.prototype.splice = function (e, t) { return arguments.length ? v.apply(this, [e === void 0 ? 0 : e, t === void 0 ? this.length - e : t].concat(u.call(arguments, 2))) : [] } } Array.isArray || (Array.isArray = function (t) { return a(t) == "[object Array]" }); var m = Object("a"), g = m[0] != "a" || !(0 in m); Array.prototype.forEach || (Array.prototype.forEach = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = arguments[1], s = -1, o = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError; while (++s < o) s in r && t.call(i, r[s], s, n) }), Array.prototype.map || (Array.prototype.map = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = Array(i), o = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var u = 0; u < i; u++)u in r && (s[u] = t.call(o, r[u], u, n)); return s }), Array.prototype.filter || (Array.prototype.filter = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = [], o, u = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var f = 0; f < i; f++)f in r && (o = r[f], t.call(u, o, f, n) && s.push(o)); return s }), Array.prototype.every || (Array.prototype.every = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var o = 0; o < i; o++)if (o in r && !t.call(s, r[o], o, n)) return !1; return !0 }), Array.prototype.some || (Array.prototype.some = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0, s = arguments[1]; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); for (var o = 0; o < i; o++)if (o in r && t.call(s, r[o], o, n)) return !0; return !1 }), Array.prototype.reduce || (Array.prototype.reduce = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); if (!i && arguments.length == 1) throw new TypeError("reduce of empty array with no initial value"); var s = 0, o; if (arguments.length >= 2) o = arguments[1]; else do { if (s in r) { o = r[s++]; break } if (++s >= i) throw new TypeError("reduce of empty array with no initial value") } while (!0); for (; s < i; s++)s in r && (o = t.call(void 0, o, r[s], s, n)); return o }), Array.prototype.reduceRight || (Array.prototype.reduceRight = function (t) { var n = F(this), r = g && a(this) == "[object String]" ? this.split("") : n, i = r.length >>> 0; if (a(t) != "[object Function]") throw new TypeError(t + " is not a function"); if (!i && arguments.length == 1) throw new TypeError("reduceRight of empty array with no initial value"); var s, o = i - 1; if (arguments.length >= 2) s = arguments[1]; else do { if (o in r) { s = r[o--]; break } if (--o < 0) throw new TypeError("reduceRight of empty array with no initial value") } while (!0); do o in this && (s = t.call(void 0, s, r[o], o, n)); while (o--); return s }); if (!Array.prototype.indexOf || [0, 1].indexOf(1, 2) != -1) Array.prototype.indexOf = function (t) { var n = g && a(this) == "[object String]" ? this.split("") : F(this), r = n.length >>> 0; if (!r) return -1; var i = 0; arguments.length > 1 && (i = H(arguments[1])), i = i >= 0 ? i : Math.max(0, r + i); for (; i < r; i++)if (i in n && n[i] === t) return i; return -1 }; if (!Array.prototype.lastIndexOf || [0, 1].lastIndexOf(0, -3) != -1) Array.prototype.lastIndexOf = function (t) { var n = g && a(this) == "[object String]" ? this.split("") : F(this), r = n.length >>> 0; if (!r) return -1; var i = r - 1; arguments.length > 1 && (i = Math.min(i, H(arguments[1]))), i = i >= 0 ? i : r - Math.abs(i); for (; i >= 0; i--)if (i in n && t === n[i]) return i; return -1 }; Object.getPrototypeOf || (Object.getPrototypeOf = function (t) { return t.__proto__ || (t.constructor ? t.constructor.prototype : o) }); if (!Object.getOwnPropertyDescriptor) { var y = "Object.getOwnPropertyDescriptor called on a non-object: "; Object.getOwnPropertyDescriptor = function (t, n) { if (typeof t != "object" && typeof t != "function" || t === null) throw new TypeError(y + t); if (!f(t, n)) return; var r, i, s; r = { enumerable: !0, configurable: !0 }; if (d) { var u = t.__proto__; t.__proto__ = o; var i = h(t, n), s = p(t, n); t.__proto__ = u; if (i || s) return i && (r.get = i), s && (r.set = s), r } return r.value = t[n], r } } Object.getOwnPropertyNames || (Object.getOwnPropertyNames = function (t) { return Object.keys(t) }); if (!Object.create) { var b; Object.prototype.__proto__ === null ? b = function () { return { __proto__: null } } : b = function () { var e = {}; for (var t in e) e[t] = null; return e.constructor = e.hasOwnProperty = e.propertyIsEnumerable = e.isPrototypeOf = e.toLocaleString = e.toString = e.valueOf = e.__proto__ = null, e }, Object.create = function (t, n) { var r; if (t === null) r = b(); else { if (typeof t != "object") throw new TypeError("typeof prototype[" + typeof t + "] != 'object'"); var i = function () { }; i.prototype = t, r = new i, r.__proto__ = t } return n !== void 0 && Object.defineProperties(r, n), r } } if (Object.defineProperty) { var E = w({}), S = typeof document == "undefined" || w(document.createElement("div")); if (!E || !S) var x = Object.defineProperty } if (!Object.defineProperty || x) { var T = "Property description must be an object: ", N = "Object.defineProperty called on non-object: ", C = "getters & setters can not be defined on this javascript engine"; Object.defineProperty = function (t, n, r) { if (typeof t != "object" && typeof t != "function" || t === null) throw new TypeError(N + t); if (typeof r != "object" && typeof r != "function" || r === null) throw new TypeError(T + r); if (x) try { return x.call(Object, t, n, r) } catch (i) { } if (f(r, "value")) if (d && (h(t, n) || p(t, n))) { var s = t.__proto__; t.__proto__ = o, delete t[n], t[n] = r.value, t.__proto__ = s } else t[n] = r.value; else { if (!d) throw new TypeError(C); f(r, "get") && l(t, n, r.get), f(r, "set") && c(t, n, r.set) } return t } } Object.defineProperties || (Object.defineProperties = function (t, n) { for (var r in n) f(n, r) && Object.defineProperty(t, r, n[r]); return t }), Object.seal || (Object.seal = function (t) { return t }), Object.freeze || (Object.freeze = function (t) { return t }); try { Object.freeze(function () { }) } catch (k) { Object.freeze = function (t) { return function (n) { return typeof n == "function" ? n : t(n) } }(Object.freeze) } Object.preventExtensions || (Object.preventExtensions = function (t) { return t }), Object.isSealed || (Object.isSealed = function (t) { return !1 }), Object.isFrozen || (Object.isFrozen = function (t) { return !1 }), Object.isExtensible || (Object.isExtensible = function (t) { if (Object(t) === t) throw new TypeError; var n = ""; while (f(t, n)) n += "?"; t[n] = !0; var r = f(t, n); return delete t[n], r }); if (!Object.keys) { var L = !0, A = ["toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "constructor"], O = A.length; for (var M in { toString: null }) L = !1; Object.keys = function I(e) { if (typeof e != "object" && typeof e != "function" || e === null) throw new TypeError("Object.keys called on a non-object"); var I = []; for (var t in e) f(e, t) && I.push(t); if (L) for (var n = 0, r = O; n < r; n++) { var i = A[n]; f(e, i) && I.push(i) } return I } } Date.now || (Date.now = function () { return (new Date).getTime() }); var _ = " \n\x0b\f\r \u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\ufeff"; if (!String.prototype.trim) { _ = "[" + _ + "]"; var D = new RegExp("^" + _ + _ + "*"), P = new RegExp(_ + _ + "*$"); String.prototype.trim = function () { return String(this).replace(D, "").replace(P, "") } } var F = function (e) { if (e == null) throw new TypeError("can't convert " + e + " to object"); return Object(e) } }), define("ace/lib/fixoldbrowsers", ["require", "exports", "module", "ace/lib/regexp", "ace/lib/es5-shim"], function (e, t, n) { "use strict"; e("./regexp"), e("./es5-shim"), typeof Element != "undefined" && !Element.prototype.remove && Object.defineProperty(Element.prototype, "remove", { enumerable: !1, writable: !0, configurable: !0, value: function () { this.parentNode && this.parentNode.removeChild(this) } }) }), define("ace/lib/useragent", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.OS = { LINUX: "LINUX", MAC: "MAC", WINDOWS: "WINDOWS" }, t.getOS = function () { return t.isMac ? t.OS.MAC : t.isLinux ? t.OS.LINUX : t.OS.WINDOWS }; var r = typeof navigator == "object" ? navigator : {}, i = (/mac|win|linux/i.exec(r.platform) || ["other"])[0].toLowerCase(), s = r.userAgent || "", o = r.appName || ""; t.isWin = i == "win", t.isMac = i == "mac", t.isLinux = i == "linux", t.isIE = o == "Microsoft Internet Explorer" || o.indexOf("MSAppHost") >= 0 ? parseFloat((s.match(/(?:MSIE |Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/) || [])[1]) : parseFloat((s.match(/(?:Trident\/[0-9]+[\.0-9]+;.*rv:)([0-9]+[\.0-9]+)/) || [])[1]), t.isOldIE = t.isIE && t.isIE < 9, t.isGecko = t.isMozilla = s.match(/ Gecko\/\d+/), t.isOpera = typeof opera == "object" && Object.prototype.toString.call(window.opera) == "[object Opera]", t.isWebKit = parseFloat(s.split("WebKit/")[1]) || undefined, t.isChrome = parseFloat(s.split(" Chrome/")[1]) || undefined, t.isEdge = parseFloat(s.split(" Edge/")[1]) || undefined, t.isAIR = s.indexOf("AdobeAIR") >= 0, t.isAndroid = s.indexOf("Android") >= 0, t.isChromeOS = s.indexOf(" CrOS ") >= 0, t.isIOS = /iPad|iPhone|iPod/.test(s) && !window.MSStream, t.isIOS && (t.isMac = !0), t.isMobile = t.isIOS || t.isAndroid }), define("ace/lib/dom", ["require", "exports", "module", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("./useragent"), i = "http://www.w3.org/1999/xhtml"; t.buildDom = function o(e, t, n) { if (typeof e == "string" && e) { var r = document.createTextNode(e); return t && t.appendChild(r), r } if (!Array.isArray(e)) return e && e.appendChild && t && t.appendChild(e), e; if (typeof e[0] != "string" || !e[0]) { var i = []; for (var s = 0; s < e.length; s++) { var u = o(e[s], t, n); u && i.push(u) } return i } var a = document.createElement(e[0]), f = e[1], l = 1; f && typeof f == "object" && !Array.isArray(f) && (l = 2); for (var s = l; s < e.length; s++)o(e[s], a, n); return l == 2 && Object.keys(f).forEach(function (e) { var t = f[e]; e === "class" ? a.className = Array.isArray(t) ? t.join(" ") : t : typeof t == "function" || e == "value" || e[0] == "$" ? a[e] = t : e === "ref" ? n && (n[t] = a) : t != null && a.setAttribute(e, t) }), t && t.appendChild(a), a }, t.getDocumentHead = function (e) { return e || (e = document), e.head || e.getElementsByTagName("head")[0] || e.documentElement }, t.createElement = function (e, t) { return document.createElementNS ? document.createElementNS(t || i, e) : document.createElement(e) }, t.removeChildren = function (e) { e.innerHTML = "" }, t.createTextNode = function (e, t) { var n = t ? t.ownerDocument : document; return n.createTextNode(e) }, t.createFragment = function (e) { var t = e ? e.ownerDocument : document; return t.createDocumentFragment() }, t.hasCssClass = function (e, t) { var n = (e.className + "").split(/\s+/g); return n.indexOf(t) !== -1 }, t.addCssClass = function (e, n) { t.hasCssClass(e, n) || (e.className += " " + n) }, t.removeCssClass = function (e, t) { var n = e.className.split(/\s+/g); for (; ;) { var r = n.indexOf(t); if (r == -1) break; n.splice(r, 1) } e.className = n.join(" ") }, t.toggleCssClass = function (e, t) { var n = e.className.split(/\s+/g), r = !0; for (; ;) { var i = n.indexOf(t); if (i == -1) break; r = !1, n.splice(i, 1) } return r && n.push(t), e.className = n.join(" "), r }, t.setCssClass = function (e, n, r) { r ? t.addCssClass(e, n) : t.removeCssClass(e, n) }, t.hasCssString = function (e, t) { var n = 0, r; t = t || document; if (r = t.querySelectorAll("style")) while (n < r.length) if (r[n++].id === e) return !0 }, t.importCssString = function (n, r, i) { var s = i; if (!i || !i.getRootNode) s = document; else { s = i.getRootNode(); if (!s || s == i) s = document } var o = s.ownerDocument || s; if (r && t.hasCssString(r, s)) return null; r && (n += "\n/*# sourceURL=ace/css/" + r + " */"); var u = t.createElement("style"); u.appendChild(o.createTextNode(n)), r && (u.id = r), s == o && (s = t.getDocumentHead(o)), s.insertBefore(u, s.firstChild) }, t.importCssStylsheet = function (e, n) { t.buildDom(["link", { rel: "stylesheet", href: e }], t.getDocumentHead(n)) }, t.scrollbarWidth = function (e) { var n = t.createElement("ace_inner"); n.style.width = "100%", n.style.minWidth = "0px", n.style.height = "200px", n.style.display = "block"; var r = t.createElement("ace_outer"), i = r.style; i.position = "absolute", i.left = "-10000px", i.overflow = "hidden", i.width = "200px", i.minWidth = "0px", i.height = "150px", i.display = "block", r.appendChild(n); var s = e.documentElement; s.appendChild(r); var o = n.offsetWidth; i.overflow = "scroll"; var u = n.offsetWidth; return o == u && (u = r.clientWidth), s.removeChild(r), o - u }, typeof document == "undefined" && (t.importCssString = function () { }), t.computedStyle = function (e, t) { return window.getComputedStyle(e, "") || {} }, t.setStyle = function (e, t, n) { e[t] !== n && (e[t] = n) }, t.HAS_CSS_ANIMATION = !1, t.HAS_CSS_TRANSFORMS = !1, t.HI_DPI = r.isWin ? typeof window != "undefined" && window.devicePixelRatio >= 1.5 : !0; if (typeof document != "undefined") { var s = document.createElement("div"); t.HI_DPI && s.style.transform !== undefined && (t.HAS_CSS_TRANSFORMS = !0), !r.isEdge && typeof s.style.animationName != "undefined" && (t.HAS_CSS_ANIMATION = !0), s = null } t.HAS_CSS_TRANSFORMS ? t.translate = function (e, t, n) { e.style.transform = "translate(" + Math.round(t) + "px, " + Math.round(n) + "px)" } : t.translate = function (e, t, n) { e.style.top = Math.round(n) + "px", e.style.left = Math.round(t) + "px" } }), define("ace/lib/oop", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.inherits = function (e, t) { e.super_ = t, e.prototype = Object.create(t.prototype, { constructor: { value: e, enumerable: !1, writable: !0, configurable: !0 } }) }, t.mixin = function (e, t) { for (var n in t) e[n] = t[n]; return e }, t.implement = function (e, n) { t.mixin(e, n) } }), define("ace/lib/keys", ["require", "exports", "module", "ace/lib/oop"], function (e, t, n) { "use strict"; var r = e("./oop"), i = function () { var e = { MODIFIER_KEYS: { 16: "Shift", 17: "Ctrl", 18: "Alt", 224: "Meta", 91: "MetaLeft", 92: "MetaRight", 93: "ContextMenu" }, KEY_MODS: { ctrl: 1, alt: 2, option: 2, shift: 4, "super": 8, meta: 8, command: 8, cmd: 8, control: 1 }, FUNCTION_KEYS: { 8: "Backspace", 9: "Tab", 13: "Return", 19: "Pause", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "Print", 45: "Insert", 46: "Delete", 96: "Numpad0", 97: "Numpad1", 98: "Numpad2", 99: "Numpad3", 100: "Numpad4", 101: "Numpad5", 102: "Numpad6", 103: "Numpad7", 104: "Numpad8", 105: "Numpad9", "-13": "NumpadEnter", 112: "F1", 113: "F2", 114: "F3", 115: "F4", 116: "F5", 117: "F6", 118: "F7", 119: "F8", 120: "F9", 121: "F10", 122: "F11", 123: "F12", 144: "Numlock", 145: "Scrolllock" }, PRINTABLE_KEYS: { 32: " ", 48: "0", 49: "1", 50: "2", 51: "3", 52: "4", 53: "5", 54: "6", 55: "7", 56: "8", 57: "9", 59: ";", 61: "=", 65: "a", 66: "b", 67: "c", 68: "d", 69: "e", 70: "f", 71: "g", 72: "h", 73: "i", 74: "j", 75: "k", 76: "l", 77: "m", 78: "n", 79: "o", 80: "p", 81: "q", 82: "r", 83: "s", 84: "t", 85: "u", 86: "v", 87: "w", 88: "x", 89: "y", 90: "z", 107: "+", 109: "-", 110: ".", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'", 111: "/", 106: "*" } }, t, n; for (n in e.FUNCTION_KEYS) t = e.FUNCTION_KEYS[n].toLowerCase(), e[t] = parseInt(n, 10); for (n in e.PRINTABLE_KEYS) t = e.PRINTABLE_KEYS[n].toLowerCase(), e[t] = parseInt(n, 10); return r.mixin(e, e.MODIFIER_KEYS), r.mixin(e, e.PRINTABLE_KEYS), r.mixin(e, e.FUNCTION_KEYS), e.enter = e["return"], e.escape = e.esc, e.del = e["delete"], e[173] = "-", function () { var t = ["cmd", "ctrl", "alt", "shift"]; for (var n = Math.pow(2, t.length); n--;)e.KEY_MODS[n] = t.filter(function (t) { return n & e.KEY_MODS[t] }).join("-") + "-" }(), e.KEY_MODS[0] = "", e.KEY_MODS[-1] = "input-", e }(); r.mixin(t, i), t.keyCodeToString = function (e) { var t = i[e]; return typeof t != "string" && (t = String.fromCharCode(e)), t.toLowerCase() } }), define("ace/lib/event", ["require", "exports", "module", "ace/lib/keys", "ace/lib/useragent"], function (e, t, n) { "use strict"; function a() { u = !1; try { document.createComment("").addEventListener("test", function () { }, { get passive() { u = { passive: !1 } } }) } catch (e) { } } function f() { return u == undefined && a(), u } function l(e, t, n) { this.elem = e, this.type = t, this.callback = n } function d(e, t, n) { var u = p(t); if (!i.isMac && s) { t.getModifierState && (t.getModifierState("OS") || t.getModifierState("Win")) && (u |= 8); if (s.altGr) { if ((3 & u) == 3) return; s.altGr = 0 } if (n === 18 || n === 17) { var a = "location" in t ? t.location : t.keyLocation; if (n === 17 && a === 1) s[n] == 1 && (o = t.timeStamp); else if (n === 18 && u === 3 && a === 2) { var f = t.timeStamp - o; f < 50 && (s.altGr = !0) } } } n in r.MODIFIER_KEYS && (n = -1); if (!u && n === 13) { var a = "location" in t ? t.location : t.keyLocation; if (a === 3) { e(t, u, -n); if (t.defaultPrevented) return } } if (i.isChromeOS && u & 8) { e(t, u, n); if (t.defaultPrevented) return; u &= -9 } return !!u || n in r.FUNCTION_KEYS || n in r.PRINTABLE_KEYS ? e(t, u, n) : !1 } function v() { s = Object.create(null) } var r = e("./keys"), i = e("./useragent"), s = null, o = 0, u; l.prototype.destroy = function () { h(this.elem, this.type, this.callback), this.elem = this.type = this.callback = undefined }; var c = t.addListener = function (e, t, n, r) { e.addEventListener(t, n, f()), r && r.$toDestroy.push(new l(e, t, n)) }, h = t.removeListener = function (e, t, n) { e.removeEventListener(t, n, f()) }; t.stopEvent = function (e) { return t.stopPropagation(e), t.preventDefault(e), !1 }, t.stopPropagation = function (e) { e.stopPropagation && e.stopPropagation() }, t.preventDefault = function (e) { e.preventDefault && e.preventDefault() }, t.getButton = function (e) { return e.type == "dblclick" ? 0 : e.type == "contextmenu" || i.isMac && e.ctrlKey && !e.altKey && !e.shiftKey ? 2 : e.button }, t.capture = function (e, t, n) { function r(e) { t && t(e), n && n(e), h(document, "mousemove", t), h(document, "mouseup", r), h(document, "dragstart", r) } return c(document, "mousemove", t), c(document, "mouseup", r), c(document, "dragstart", r), r }, t.addMouseWheelListener = function (e, t, n) { "onmousewheel" in e ? c(e, "mousewheel", function (e) { var n = 8; e.wheelDeltaX !== undefined ? (e.wheelX = -e.wheelDeltaX / n, e.wheelY = -e.wheelDeltaY / n) : (e.wheelX = 0, e.wheelY = -e.wheelDelta / n), t(e) }, n) : "onwheel" in e ? c(e, "wheel", function (e) { var n = .35; switch (e.deltaMode) { case e.DOM_DELTA_PIXEL: e.wheelX = e.deltaX * n || 0, e.wheelY = e.deltaY * n || 0; break; case e.DOM_DELTA_LINE: case e.DOM_DELTA_PAGE: e.wheelX = (e.deltaX || 0) * 5, e.wheelY = (e.deltaY || 0) * 5 }t(e) }, n) : c(e, "DOMMouseScroll", function (e) { e.axis && e.axis == e.HORIZONTAL_AXIS ? (e.wheelX = (e.detail || 0) * 5, e.wheelY = 0) : (e.wheelX = 0, e.wheelY = (e.detail || 0) * 5), t(e) }, n) }, t.addMultiMouseDownListener = function (e, n, r, s, o) { function p(e) { t.getButton(e) !== 0 ? u = 0 : e.detail > 1 ? (u++, u > 4 && (u = 1)) : u = 1; if (i.isIE) { var o = Math.abs(e.clientX - a) > 5 || Math.abs(e.clientY - f) > 5; if (!l || o) u = 1; l && clearTimeout(l), l = setTimeout(function () { l = null }, n[u - 1] || 600), u == 1 && (a = e.clientX, f = e.clientY) } e._clicks = u, r[s]("mousedown", e); if (u > 4) u = 0; else if (u > 1) return r[s](h[u], e) } var u = 0, a, f, l, h = { 2: "dblclick", 3: "tripleclick", 4: "quadclick" }; Array.isArray(e) || (e = [e]), e.forEach(function (e) { c(e, "mousedown", p, o) }) }; var p = function (e) { return 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0) | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0) }; t.getModifierString = function (e) { return r.KEY_MODS[p(e)] }, t.addCommandKeyListener = function (e, n, r) { if (i.isOldGecko || i.isOpera && !("KeyboardEvent" in window)) { var o = null; c(e, "keydown", function (e) { o = e.keyCode }, r), c(e, "keypress", function (e) { return d(n, e, o) }, r) } else { var u = null; c(e, "keydown", function (e) { s[e.keyCode] = (s[e.keyCode] || 0) + 1; var t = d(n, e, e.keyCode); return u = e.defaultPrevented, t }, r), c(e, "keypress", function (e) { u && (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) && (t.stopEvent(e), u = null) }, r), c(e, "keyup", function (e) { s[e.keyCode] = null }, r), s || (v(), c(window, "focus", v)) } }; if (typeof window == "object" && window.postMessage && !i.isOldIE) { var m = 1; t.nextTick = function (e, n) { n = n || window; var r = "zero-timeout-message-" + m++, i = function (s) { s.data == r && (t.stopPropagation(s), h(n, "message", i), e()) }; c(n, "message", i), n.postMessage(r, "*") } } t.$idleBlocked = !1, t.onIdle = function (e, n) { return setTimeout(function r() { t.$idleBlocked ? setTimeout(r, 100) : e() }, n) }, t.$idleBlockId = null, t.blockIdle = function (e) { t.$idleBlockId && clearTimeout(t.$idleBlockId), t.$idleBlocked = !0, t.$idleBlockId = setTimeout(function () { t.$idleBlocked = !1 }, e || 100) }, t.nextFrame = typeof window == "object" && (window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame), t.nextFrame ? t.nextFrame = t.nextFrame.bind(window) : t.nextFrame = function (e) { setTimeout(e, 17) } }), define("ace/range", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = function (e, t) { return e.row - t.row || e.column - t.column }, i = function (e, t, n, r) { this.start = { row: e, column: t }, this.end = { row: n, column: r } }; (function () { this.isEqual = function (e) { return this.start.row === e.start.row && this.end.row === e.end.row && this.start.column === e.start.column && this.end.column === e.end.column }, this.toString = function () { return "Range: [" + this.start.row + "/" + this.start.column + "] -> [" + this.end.row + "/" + this.end.column + "]" }, this.contains = function (e, t) { return this.compare(e, t) == 0 }, this.compareRange = function (e) { var t, n = e.end, r = e.start; return t = this.compare(n.row, n.column), t == 1 ? (t = this.compare(r.row, r.column), t == 1 ? 2 : t == 0 ? 1 : 0) : t == -1 ? -2 : (t = this.compare(r.row, r.column), t == -1 ? -1 : t == 1 ? 42 : 0) }, this.comparePoint = function (e) { return this.compare(e.row, e.column) }, this.containsRange = function (e) { return this.comparePoint(e.start) == 0 && this.comparePoint(e.end) == 0 }, this.intersects = function (e) { var t = this.compareRange(e); return t == -1 || t == 0 || t == 1 }, this.isEnd = function (e, t) { return this.end.row == e && this.end.column == t }, this.isStart = function (e, t) { return this.start.row == e && this.start.column == t }, this.setStart = function (e, t) { typeof e == "object" ? (this.start.column = e.column, this.start.row = e.row) : (this.start.row = e, this.start.column = t) }, this.setEnd = function (e, t) { typeof e == "object" ? (this.end.column = e.column, this.end.row = e.row) : (this.end.row = e, this.end.column = t) }, this.inside = function (e, t) { return this.compare(e, t) == 0 ? this.isEnd(e, t) || this.isStart(e, t) ? !1 : !0 : !1 }, this.insideStart = function (e, t) { return this.compare(e, t) == 0 ? this.isEnd(e, t) ? !1 : !0 : !1 }, this.insideEnd = function (e, t) { return this.compare(e, t) == 0 ? this.isStart(e, t) ? !1 : !0 : !1 }, this.compare = function (e, t) { return !this.isMultiLine() && e === this.start.row ? t < this.start.column ? -1 : t > this.end.column ? 1 : 0 : e < this.start.row ? -1 : e > this.end.row ? 1 : this.start.row === e ? t >= this.start.column ? 0 : -1 : this.end.row === e ? t <= this.end.column ? 0 : 1 : 0 }, this.compareStart = function (e, t) { return this.start.row == e && this.start.column == t ? -1 : this.compare(e, t) }, this.compareEnd = function (e, t) { return this.end.row == e && this.end.column == t ? 1 : this.compare(e, t) }, this.compareInside = function (e, t) { return this.end.row == e && this.end.column == t ? 1 : this.start.row == e && this.start.column == t ? -1 : this.compare(e, t) }, this.clipRows = function (e, t) { if (this.end.row > t) var n = { row: t + 1, column: 0 }; else if (this.end.row < e) var n = { row: e, column: 0 }; if (this.start.row > t) var r = { row: t + 1, column: 0 }; else if (this.start.row < e) var r = { row: e, column: 0 }; return i.fromPoints(r || this.start, n || this.end) }, this.extend = function (e, t) { var n = this.compare(e, t); if (n == 0) return this; if (n == -1) var r = { row: e, column: t }; else var s = { row: e, column: t }; return i.fromPoints(r || this.start, s || this.end) }, this.isEmpty = function () { return this.start.row === this.end.row && this.start.column === this.end.column }, this.isMultiLine = function () { return this.start.row !== this.end.row }, this.clone = function () { return i.fromPoints(this.start, this.end) }, this.collapseRows = function () { return this.end.column == 0 ? new i(this.start.row, 0, Math.max(this.start.row, this.end.row - 1), 0) : new i(this.start.row, 0, this.end.row, 0) }, this.toScreenRange = function (e) { var t = e.documentToScreenPosition(this.start), n = e.documentToScreenPosition(this.end); return new i(t.row, t.column, n.row, n.column) }, this.moveBy = function (e, t) { this.start.row += e, this.start.column += t, this.end.row += e, this.end.column += t } }).call(i.prototype), i.fromPoints = function (e, t) { return new i(e.row, e.column, t.row, t.column) }, i.comparePoints = r, i.comparePoints = function (e, t) { return e.row - t.row || e.column - t.column }, t.Range = i }), define("ace/lib/lang", ["require", "exports", "module"], function (e, t, n) { "use strict"; t.last = function (e) { return e[e.length - 1] }, t.stringReverse = function (e) { return e.split("").reverse().join("") }, t.stringRepeat = function (e, t) { var n = ""; while (t > 0) { t & 1 && (n += e); if (t >>= 1) e += e } return n }; var r = /^\s\s*/, i = /\s\s*$/; t.stringTrimLeft = function (e) { return e.replace(r, "") }, t.stringTrimRight = function (e) { return e.replace(i, "") }, t.copyObject = function (e) { var t = {}; for (var n in e) t[n] = e[n]; return t }, t.copyArray = function (e) { var t = []; for (var n = 0, r = e.length; n < r; n++)e[n] && typeof e[n] == "object" ? t[n] = this.copyObject(e[n]) : t[n] = e[n]; return t }, t.deepCopy = function s(e) { if (typeof e != "object" || !e) return e; var t; if (Array.isArray(e)) { t = []; for (var n = 0; n < e.length; n++)t[n] = s(e[n]); return t } if (Object.prototype.toString.call(e) !== "[object Object]") return e; t = {}; for (var n in e) t[n] = s(e[n]); return t }, t.arrayToMap = function (e) { var t = {}; for (var n = 0; n < e.length; n++)t[e[n]] = 1; return t }, t.createMap = function (e) { var t = Object.create(null); for (var n in e) t[n] = e[n]; return t }, t.arrayRemove = function (e, t) { for (var n = 0; n <= e.length; n++)t === e[n] && e.splice(n, 1) }, t.escapeRegExp = function (e) { return e.replace(/([.*+?^${}()|[\]\/\\])/g, "\\$1") }, t.escapeHTML = function (e) { return ("" + e).replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/ Date.now() - 50 ? !0 : r = !1 }, cancel: function () { r = Date.now() } } }), define("ace/keyboard/textinput", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent", "ace/lib/dom", "ace/lib/lang", "ace/clipboard", "ace/lib/keys"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = e("../lib/dom"), o = e("../lib/lang"), u = e("../clipboard"), a = i.isChrome < 18, f = i.isIE, l = i.isChrome > 63, c = 400, h = e("../lib/keys"), p = h.KEY_MODS, d = i.isIOS, v = d ? /\s/ : /\n/, m = i.isMobile, g = function (e, t) { function X() { x = !0, n.blur(), n.focus(), x = !1 } function $(e) { e.keyCode == 27 && n.value.length < n.selectionStart && (b || (T = n.value), N = C = -1, O()), V() } function K() { clearTimeout(J), J = setTimeout(function () { E && (n.style.cssText = E, E = ""), t.renderer.$isMousePressed = !1, t.renderer.$keepTextAreaAtCursor && t.renderer.$moveTextAreaToCursor() }, 0) } function G(e, t, n) { var r = null, i = !1; n.addEventListener("keydown", function (e) { r && clearTimeout(r), i = !0 }, !0), n.addEventListener("keyup", function (e) { r = setTimeout(function () { i = !1 }, 100) }, !0); var s = function (e) { if (document.activeElement !== n) return; if (i || b || t.$mouseHandler.isMousePressed) return; if (g) return; var r = n.selectionStart, s = n.selectionEnd, o = null, u = 0; if (r == 0) o = h.up; else if (r == 1) o = h.home; else if (s > C && T[s] == "\n") o = h.end; else if (r < N && T[r - 1] == " ") o = h.left, u = p.option; else if (r < N || r == N && C != N && r == s) o = h.left; else if (s > C && T.slice(0, s).split("\n").length > 2) o = h.down; else if (s > C && T[s - 1] == " ") o = h.right, u = p.option; else if (s > C || s == C && C != N && r == s) o = h.right; r !== s && (u |= p.shift); if (o) { var a = t.onCommandKey({}, u, o); if (!a && t.commands) { o = h.keyCodeToString(o); var f = t.commands.findKeyCommand(u, o); f && t.execCommand(f) } N = r, C = s, O("") } }; document.addEventListener("selectionchange", s), t.on("destroy", function () { document.removeEventListener("selectionchange", s) }) } var n = s.createElement("textarea"); n.className = "ace_text-input", n.setAttribute("wrap", "off"), n.setAttribute("autocorrect", "off"), n.setAttribute("autocapitalize", "off"), n.setAttribute("spellcheck", !1), n.style.opacity = "0", e.insertBefore(n, e.firstChild); var g = !1, y = !1, b = !1, w = !1, E = ""; m || (n.style.fontSize = "1px"); var S = !1, x = !1, T = "", N = 0, C = 0, k = 0; try { var L = document.activeElement === n } catch (A) { } r.addListener(n, "blur", function (e) { if (x) return; t.onBlur(e), L = !1 }, t), r.addListener(n, "focus", function (e) { if (x) return; L = !0; if (i.isEdge) try { if (!document.hasFocus()) return } catch (e) { } t.onFocus(e), i.isEdge ? setTimeout(O) : O() }, t), this.$focusScroll = !1, this.focus = function () { if (E || l || this.$focusScroll == "browser") return n.focus({ preventScroll: !0 }); var e = n.style.top; n.style.position = "fixed", n.style.top = "0px"; try { var t = n.getBoundingClientRect().top != 0 } catch (r) { return } var i = []; if (t) { var s = n.parentElement; while (s && s.nodeType == 1) i.push(s), s.setAttribute("ace_nocontext", !0), !s.parentElement && s.getRootNode ? s = s.getRootNode().host : s = s.parentElement } n.focus({ preventScroll: !0 }), t && i.forEach(function (e) { e.removeAttribute("ace_nocontext") }), setTimeout(function () { n.style.position = "", n.style.top == "0px" && (n.style.top = e) }, 0) }, this.blur = function () { n.blur() }, this.isFocused = function () { return L }, t.on("beforeEndOperation", function () { var e = t.curOp, r = e && e.command && e.command.name; if (r == "insertstring") return; var i = r && (e.docChanged || e.selectionChanged); b && i && (T = n.value = "", W()), O() }); var O = d ? function (e) { if (!L || g && !e || w) return; e || (e = ""); var r = "\n ab" + e + "cde fg\n"; r != n.value && (n.value = T = r); var i = 4, s = 4 + (e.length || (t.selection.isEmpty() ? 0 : 1)); (N != i || C != s) && n.setSelectionRange(i, s), N = i, C = s } : function () { if (b || w) return; if (!L && !P) return; b = !0; var e = 0, r = 0, i = ""; if (t.session) { var s = t.selection, o = s.getRange(), u = s.cursor.row; e = o.start.column, r = o.end.column, i = t.session.getLine(u); if (o.start.row != u) { var a = t.session.getLine(u - 1); e = o.start.row < u - 1 ? 0 : e, r += a.length + 1, i = a + "\n" + i } else if (o.end.row != u) { var f = t.session.getLine(u + 1); r = o.end.row > u + 1 ? f.length : r, r += i.length + 1, i = i + "\n" + f } else m && u > 0 && (i = "\n" + i, r += 1, e += 1); i.length > c && (e < c && r < c ? i = i.slice(0, c) : (i = "\n", e = 0, r = 1)) } var l = i + "\n\n"; l != T && (n.value = T = l, N = C = l.length), P && (N = n.selectionStart, C = n.selectionEnd); if (C != r || N != e || n.selectionEnd != C) try { n.setSelectionRange(e, r), N = e, C = r } catch (h) { } b = !1 }; this.resetSelection = O, L && t.onFocus(); var M = function (e) { return e.selectionStart === 0 && e.selectionEnd >= T.length && e.value === T && T && e.selectionEnd !== C }, _ = function (e) { if (b) return; g ? g = !1 : M(n) ? (t.selectAll(), O()) : m && n.selectionStart != N && O() }, D = null; this.setInputHandler = function (e) { D = e }, this.getInputHandler = function () { return D }; var P = !1, H = function (e, r) { P && (P = !1); if (y) return O(), e && t.onPaste(e), y = !1, ""; var i = n.selectionStart, s = n.selectionEnd, o = N, u = T.length - C, a = e, f = e.length - i, l = e.length - s, c = 0; while (o > 0 && T[c] == e[c]) c++, o--; a = a.slice(c), c = 1; while (u > 0 && T.length - c > N - 1 && T[T.length - c] == e[e.length - c]) c++, u--; f -= c - 1, l -= c - 1; var h = a.length - c + 1; return h < 0 && (o = -h, h = 0), a = a.slice(0, h), !r && !a && !f && !o && !u && !l ? "" : (w = !0, a && !o && !u && !f && !l || S ? t.onTextInput(a) : t.onTextInput(a, { extendLeft: o, extendRight: u, restoreStart: f, restoreEnd: l }), w = !1, T = e, N = i, C = s, k = l, a) }, B = function (e) { if (b) return z(); if (e && e.inputType) { if (e.inputType == "historyUndo") return t.execCommand("undo"); if (e.inputType == "historyRedo") return t.execCommand("redo") } var r = n.value, i = H(r, !0); (r.length > c + 100 || v.test(i) || m && N < 1 && N == C) && O() }, j = function (e, t, n) { var r = e.clipboardData || window.clipboardData; if (!r || a) return; var i = f || n ? "Text" : "text/plain"; try { return t ? r.setData(i, t) !== !1 : r.getData(i) } catch (e) { if (!n) return j(e, t, !0) } }, F = function (e, i) { var s = t.getCopyText(); if (!s) return r.preventDefault(e); j(e, s) ? (d && (O(s), g = s, setTimeout(function () { g = !1 }, 10)), i ? t.onCut() : t.onCopy(), r.preventDefault(e)) : (g = !0, n.value = s, n.select(), setTimeout(function () { g = !1, O(), i ? t.onCut() : t.onCopy() })) }, I = function (e) { F(e, !0) }, q = function (e) { F(e, !1) }, R = function (e) { var s = j(e); if (u.pasteCancelled()) return; typeof s == "string" ? (s && t.onPaste(s, e), i.isIE && setTimeout(O), r.preventDefault(e)) : (n.value = "", y = !0) }; r.addCommandKeyListener(n, t.onCommandKey.bind(t), t), r.addListener(n, "select", _, t), r.addListener(n, "input", B, t), r.addListener(n, "cut", I, t), r.addListener(n, "copy", q, t), r.addListener(n, "paste", R, t), (!("oncut" in n) || !("oncopy" in n) || !("onpaste" in n)) && r.addListener(e, "keydown", function (e) { if (i.isMac && !e.metaKey || !e.ctrlKey) return; switch (e.keyCode) { case 67: q(e); break; case 86: R(e); break; case 88: I(e) } }, t); var U = function (e) { if (b || !t.onCompositionStart || t.$readOnly) return; b = {}; if (S) return; e.data && (b.useTextareaForIME = !1), setTimeout(z, 0), t._signal("compositionStart"), t.on("mousedown", X); var r = t.getSelectionRange(); r.end.row = r.start.row, r.end.column = r.start.column, b.markerRange = r, b.selectionStart = N, t.onCompositionStart(b), b.useTextareaForIME ? (T = n.value = "", N = 0, C = 0) : (n.msGetInputContext && (b.context = n.msGetInputContext()), n.getInputContext && (b.context = n.getInputContext())) }, z = function () { if (!b || !t.onCompositionUpdate || t.$readOnly) return; if (S) return X(); if (b.useTextareaForIME) t.onCompositionUpdate(n.value); else { var e = n.value; H(e), b.markerRange && (b.context && (b.markerRange.start.column = b.selectionStart = b.context.compositionStartOffset), b.markerRange.end.column = b.markerRange.start.column + C - b.selectionStart + k) } }, W = function (e) { if (!t.onCompositionEnd || t.$readOnly) return; b = !1, t.onCompositionEnd(), t.off("mousedown", X), e && B() }, V = o.delayedCall(z, 50).schedule.bind(null, null); r.addListener(n, "compositionstart", U, t), r.addListener(n, "compositionupdate", z, t), r.addListener(n, "keyup", $, t), r.addListener(n, "keydown", V, t), r.addListener(n, "compositionend", W, t), this.getElement = function () { return n }, this.setCommandMode = function (e) { S = e, n.readOnly = !1 }, this.setReadOnly = function (e) { S || (n.readOnly = e) }, this.setCopyWithEmptySelection = function (e) { }, this.onContextMenu = function (e) { P = !0, O(), t._emit("nativecontextmenu", { target: t, domEvent: e }), this.moveToMouse(e, !0) }, this.moveToMouse = function (e, o) { E || (E = n.style.cssText), n.style.cssText = (o ? "z-index:100000;" : "") + (i.isIE ? "opacity:0.1;" : "") + "text-indent: -" + (N + C) * t.renderer.characterWidth * .5 + "px;"; var u = t.container.getBoundingClientRect(), a = s.computedStyle(t.container), f = u.top + (parseInt(a.borderTopWidth) || 0), l = u.left + (parseInt(u.borderLeftWidth) || 0), c = u.bottom - f - n.clientHeight - 2, h = function (e) { s.translate(n, e.clientX - l - 2, Math.min(e.clientY - f - 2, c)) }; h(e); if (e.type != "mousedown") return; t.renderer.$isMousePressed = !0, clearTimeout(J), i.isWin && r.capture(t.container, h, K) }, this.onContextMenuClose = K; var J, Q = function (e) { t.textInput.onContextMenu(e), K() }; r.addListener(n, "mouseup", Q, t), r.addListener(n, "mousedown", function (e) { e.preventDefault(), K() }, t), r.addListener(t.renderer.scroller, "contextmenu", Q, t), r.addListener(n, "contextmenu", Q, t), d && G(e, t, n) }; t.TextInput = g, t.$setUserAgentForTests = function (e, t) { m = e, d = t } }), define("ace/mouse/default_handlers", ["require", "exports", "module", "ace/lib/useragent"], function (e, t, n) { "use strict"; function o(e) { e.$clickSelection = null; var t = e.editor; t.setDefaultHandler("mousedown", this.onMouseDown.bind(e)), t.setDefaultHandler("dblclick", this.onDoubleClick.bind(e)), t.setDefaultHandler("tripleclick", this.onTripleClick.bind(e)), t.setDefaultHandler("quadclick", this.onQuadClick.bind(e)), t.setDefaultHandler("mousewheel", this.onMouseWheel.bind(e)); var n = ["select", "startSelect", "selectEnd", "selectAllEnd", "selectByWordsEnd", "selectByLinesEnd", "dragWait", "dragWaitEnd", "focusWait"]; n.forEach(function (t) { e[t] = this[t] }, this), e.selectByLines = this.extendSelectionBy.bind(e, "getLineRange"), e.selectByWords = this.extendSelectionBy.bind(e, "getWordRange") } function u(e, t, n, r) { return Math.sqrt(Math.pow(n - e, 2) + Math.pow(r - t, 2)) } function a(e, t) { if (e.start.row == e.end.row) var n = 2 * t.column - e.start.column - e.end.column; else if (e.start.row == e.end.row - 1 && !e.start.column && !e.end.column) var n = t.column - 4; else var n = 2 * t.row - e.start.row - e.end.row; return n < 0 ? { cursor: e.start, anchor: e.end } : { cursor: e.end, anchor: e.start } } var r = e("../lib/useragent"), i = 0, s = 550; (function () { this.onMouseDown = function (e) { var t = e.inSelection(), n = e.getDocumentPosition(); this.mousedownEvent = e; var i = this.editor, s = e.getButton(); if (s !== 0) { var o = i.getSelectionRange(), u = o.isEmpty(); (u || s == 1) && i.selection.moveToPosition(n), s == 2 && (i.textInput.onContextMenu(e.domEvent), r.isMozilla || e.preventDefault()); return } this.mousedownEvent.time = Date.now(); if (t && !i.isFocused()) { i.focus(); if (this.$focusTimeout && !this.$clickSelection && !i.inMultiSelectMode) { this.setState("focusWait"), this.captureMouse(e); return } } return this.captureMouse(e), this.startSelect(n, e.domEvent._clicks > 1), e.preventDefault() }, this.startSelect = function (e, t) { e = e || this.editor.renderer.screenToTextCoordinates(this.x, this.y); var n = this.editor; if (!this.mousedownEvent) return; this.mousedownEvent.getShiftKey() ? n.selection.selectToPosition(e) : t || n.selection.moveToPosition(e), t || this.select(), n.renderer.scroller.setCapture && n.renderer.scroller.setCapture(), n.setStyle("ace_selecting"), this.setState("select") }, this.select = function () { var e, t = this.editor, n = t.renderer.screenToTextCoordinates(this.x, this.y); if (this.$clickSelection) { var r = this.$clickSelection.comparePoint(n); if (r == -1) e = this.$clickSelection.end; else if (r == 1) e = this.$clickSelection.start; else { var i = a(this.$clickSelection, n); n = i.cursor, e = i.anchor } t.selection.setSelectionAnchor(e.row, e.column) } t.selection.selectToPosition(n), t.renderer.scrollCursorIntoView() }, this.extendSelectionBy = function (e) { var t, n = this.editor, r = n.renderer.screenToTextCoordinates(this.x, this.y), i = n.selection[e](r.row, r.column); if (this.$clickSelection) { var s = this.$clickSelection.comparePoint(i.start), o = this.$clickSelection.comparePoint(i.end); if (s == -1 && o <= 0) { t = this.$clickSelection.end; if (i.end.row != r.row || i.end.column != r.column) r = i.start } else if (o == 1 && s >= 0) { t = this.$clickSelection.start; if (i.start.row != r.row || i.start.column != r.column) r = i.end } else if (s == -1 && o == 1) r = i.end, t = i.start; else { var u = a(this.$clickSelection, r); r = u.cursor, t = u.anchor } n.selection.setSelectionAnchor(t.row, t.column) } n.selection.selectToPosition(r), n.renderer.scrollCursorIntoView() }, this.selectEnd = this.selectAllEnd = this.selectByWordsEnd = this.selectByLinesEnd = function () { this.$clickSelection = null, this.editor.unsetStyle("ace_selecting"), this.editor.renderer.scroller.releaseCapture && this.editor.renderer.scroller.releaseCapture() }, this.focusWait = function () { var e = u(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y), t = Date.now(); (e > i || t - this.mousedownEvent.time > this.$focusTimeout) && this.startSelect(this.mousedownEvent.getDocumentPosition()) }, this.onDoubleClick = function (e) { var t = e.getDocumentPosition(), n = this.editor, r = n.session, i = r.getBracketRange(t); i ? (i.isEmpty() && (i.start.column--, i.end.column++), this.setState("select")) : (i = n.selection.getWordRange(t.row, t.column), this.setState("selectByWords")), this.$clickSelection = i, this.select() }, this.onTripleClick = function (e) { var t = e.getDocumentPosition(), n = this.editor; this.setState("selectByLines"); var r = n.getSelectionRange(); r.isMultiLine() && r.contains(t.row, t.column) ? (this.$clickSelection = n.selection.getLineRange(r.start.row), this.$clickSelection.end = n.selection.getLineRange(r.end.row).end) : this.$clickSelection = n.selection.getLineRange(t.row), this.select() }, this.onQuadClick = function (e) { var t = this.editor; t.selectAll(), this.$clickSelection = t.getSelectionRange(), this.setState("selectAll") }, this.onMouseWheel = function (e) { if (e.getAccelKey()) return; e.getShiftKey() && e.wheelY && !e.wheelX && (e.wheelX = e.wheelY, e.wheelY = 0); var t = this.editor; this.$lastScroll || (this.$lastScroll = { t: 0, vx: 0, vy: 0, allowed: 0 }); var n = this.$lastScroll, r = e.domEvent.timeStamp, i = r - n.t, o = i ? e.wheelX / i : n.vx, u = i ? e.wheelY / i : n.vy; i < s && (o = (o + n.vx) / 2, u = (u + n.vy) / 2); var a = Math.abs(o / u), f = !1; a >= 1 && t.renderer.isScrollableBy(e.wheelX * e.speed, 0) && (f = !0), a <= 1 && t.renderer.isScrollableBy(0, e.wheelY * e.speed) && (f = !0); if (f) n.allowed = r; else if (r - n.allowed < s) { var l = Math.abs(o) <= 1.5 * Math.abs(n.vx) && Math.abs(u) <= 1.5 * Math.abs(n.vy); l ? (f = !0, n.allowed = r) : n.allowed = 0 } n.t = r, n.vx = o, n.vy = u; if (f) return t.renderer.scrollBy(e.wheelX * e.speed, e.wheelY * e.speed), e.stop() } }).call(o.prototype), t.DefaultHandlers = o }), define("ace/tooltip", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom"], function (e, t, n) { "use strict"; function s(e) { this.isOpen = !1, this.$element = null, this.$parentNode = e } var r = e("./lib/oop"), i = e("./lib/dom"); (function () { this.$init = function () { return this.$element = i.createElement("div"), this.$element.className = "ace_tooltip", this.$element.style.display = "none", this.$parentNode.appendChild(this.$element), this.$element }, this.getElement = function () { return this.$element || this.$init() }, this.setText = function (e) { this.getElement().textContent = e }, this.setHtml = function (e) { this.getElement().innerHTML = e }, this.setPosition = function (e, t) { this.getElement().style.left = e + "px", this.getElement().style.top = t + "px" }, this.setClassName = function (e) { i.addCssClass(this.getElement(), e) }, this.show = function (e, t, n) { e != null && this.setText(e), t != null && n != null && this.setPosition(t, n), this.isOpen || (this.getElement().style.display = "block", this.isOpen = !0) }, this.hide = function () { this.isOpen && (this.getElement().style.display = "none", this.isOpen = !1) }, this.getHeight = function () { return this.getElement().offsetHeight }, this.getWidth = function () { return this.getElement().offsetWidth }, this.destroy = function () { this.isOpen = !1, this.$element && this.$element.parentNode && this.$element.parentNode.removeChild(this.$element) } }).call(s.prototype), t.Tooltip = s }), define("ace/mouse/default_gutter_handler", ["require", "exports", "module", "ace/lib/dom", "ace/lib/oop", "ace/lib/event", "ace/tooltip"], function (e, t, n) { "use strict"; function u(e) { function l() { var r = u.getDocumentPosition().row, s = n.$annotations[r]; if (!s) return c(); var o = t.session.getLength(); if (r == o) { var a = t.renderer.pixelToScreenCoordinates(0, u.y).row, l = u.$pos; if (a > t.session.documentToScreenRow(l.row, l.column)) return c() } if (f == s) return; f = s.text.join("
"), i.setHtml(f), i.show(), t._signal("showGutterTooltip", i), t.on("mousewheel", c); if (e.$tooltipFollowsMouse) h(u); else { var p = u.domEvent.target, d = p.getBoundingClientRect(), v = i.getElement().style; v.left = d.right + "px", v.top = d.bottom + "px" } } function c() { o && (o = clearTimeout(o)), f && (i.hide(), f = null, t._signal("hideGutterTooltip", i), t.off("mousewheel", c)) } function h(e) { i.setPosition(e.x, e.y) } var t = e.editor, n = t.renderer.$gutterLayer, i = new a(t.container); e.editor.setDefaultHandler("guttermousedown", function (r) { if (!t.isFocused() || r.getButton() != 0) return; var i = n.getRegion(r); if (i == "foldWidgets") return; var s = r.getDocumentPosition().row, o = t.session.selection; if (r.getShiftKey()) o.selectTo(s, 0); else { if (r.domEvent.detail == 2) return t.selectAll(), r.preventDefault(); e.$clickSelection = t.selection.getLineRange(s) } return e.setState("selectByLines"), e.captureMouse(r), r.preventDefault() }); var o, u, f; e.editor.setDefaultHandler("guttermousemove", function (t) { var n = t.domEvent.target || t.domEvent.srcElement; if (r.hasCssClass(n, "ace_fold-widget")) return c(); f && e.$tooltipFollowsMouse && h(t), u = t; if (o) return; o = setTimeout(function () { o = null, u && !e.isMousePressed ? l() : c() }, 50) }), s.addListener(t.renderer.$gutter, "mouseout", function (e) { u = null; if (!f || o) return; o = setTimeout(function () { o = null, c() }, 50) }, t), t.on("changeSession", c) } function a(e) { o.call(this, e) } var r = e("../lib/dom"), i = e("../lib/oop"), s = e("../lib/event"), o = e("../tooltip").Tooltip; i.inherits(a, o), function () { this.setPosition = function (e, t) { var n = window.innerWidth || document.documentElement.clientWidth, r = window.innerHeight || document.documentElement.clientHeight, i = this.getWidth(), s = this.getHeight(); e += 15, t += 15, e + i > n && (e -= e + i - n), t + s > r && (t -= 20 + s), o.prototype.setPosition.call(this, e, t) } }.call(a.prototype), t.GutterHandler = u }), define("ace/mouse/mouse_event", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = t.MouseEvent = function (e, t) { this.domEvent = e, this.editor = t, this.x = this.clientX = e.clientX, this.y = this.clientY = e.clientY, this.$pos = null, this.$inSelection = null, this.propagationStopped = !1, this.defaultPrevented = !1 }; (function () { this.stopPropagation = function () { r.stopPropagation(this.domEvent), this.propagationStopped = !0 }, this.preventDefault = function () { r.preventDefault(this.domEvent), this.defaultPrevented = !0 }, this.stop = function () { this.stopPropagation(), this.preventDefault() }, this.getDocumentPosition = function () { return this.$pos ? this.$pos : (this.$pos = this.editor.renderer.screenToTextCoordinates(this.clientX, this.clientY), this.$pos) }, this.inSelection = function () { if (this.$inSelection !== null) return this.$inSelection; var e = this.editor, t = e.getSelectionRange(); if (t.isEmpty()) this.$inSelection = !1; else { var n = this.getDocumentPosition(); this.$inSelection = t.contains(n.row, n.column) } return this.$inSelection }, this.getButton = function () { return r.getButton(this.domEvent) }, this.getShiftKey = function () { return this.domEvent.shiftKey }, this.getAccelKey = i.isMac ? function () { return this.domEvent.metaKey } : function () { return this.domEvent.ctrlKey } }).call(s.prototype) }), define("ace/mouse/dragdrop_handler", ["require", "exports", "module", "ace/lib/dom", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { "use strict"; function f(e) { function T(e, n) { var r = Date.now(), i = !n || e.row != n.row, s = !n || e.column != n.column; if (!S || i || s) t.moveCursorToPosition(e), S = r, x = { x: p, y: d }; else { var o = l(x.x, x.y, p, d); o > a ? S = null : r - S >= u && (t.renderer.scrollCursorIntoView(), S = null) } } function N(e, n) { var r = Date.now(), i = t.renderer.layerConfig.lineHeight, s = t.renderer.layerConfig.characterWidth, u = t.renderer.scroller.getBoundingClientRect(), a = { x: { left: p - u.left, right: u.right - p }, y: { top: d - u.top, bottom: u.bottom - d } }, f = Math.min(a.x.left, a.x.right), l = Math.min(a.y.top, a.y.bottom), c = { row: e.row, column: e.column }; f / s <= 2 && (c.column += a.x.left < a.x.right ? -3 : 2), l / i <= 1 && (c.row += a.y.top < a.y.bottom ? -1 : 1); var h = e.row != c.row, v = e.column != c.column, m = !n || e.row != n.row; h || v && !m ? E ? r - E >= o && t.renderer.scrollCursorIntoView(c) : E = r : E = null } function C() { var e = g; g = t.renderer.screenToTextCoordinates(p, d), T(g, e), N(g, e) } function k() { m = t.selection.toOrientedRange(), h = t.session.addMarker(m, "ace_selection", t.getSelectionStyle()), t.clearSelection(), t.isFocused() && t.renderer.$cursorLayer.setBlinking(!1), clearInterval(v), C(), v = setInterval(C, 20), y = 0, i.addListener(document, "mousemove", O) } function L() { clearInterval(v), t.session.removeMarker(h), h = null, t.selection.fromOrientedRange(m), t.isFocused() && !w && t.$resetCursorStyle(), m = null, g = null, y = 0, E = null, S = null, i.removeListener(document, "mousemove", O) } function O() { A == null && (A = setTimeout(function () { A != null && h && L() }, 20)) } function M(e) { var t = e.types; return !t || Array.prototype.some.call(t, function (e) { return e == "text/plain" || e == "Text" }) } function _(e) { var t = ["copy", "copymove", "all", "uninitialized"], n = ["move", "copymove", "linkmove", "all", "uninitialized"], r = s.isMac ? e.altKey : e.ctrlKey, i = "uninitialized"; try { i = e.dataTransfer.effectAllowed.toLowerCase() } catch (e) { } var o = "none"; return r && t.indexOf(i) >= 0 ? o = "copy" : n.indexOf(i) >= 0 ? o = "move" : t.indexOf(i) >= 0 && (o = "copy"), o } var t = e.editor, n = r.createElement("img"); n.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==", s.isOpera && (n.style.cssText = "width:1px;height:1px;position:fixed;top:0;left:0;z-index:2147483647;opacity:0;"); var f = ["dragWait", "dragWaitEnd", "startDrag", "dragReadyEnd", "onMouseDrag"]; f.forEach(function (t) { e[t] = this[t] }, this), t.on("mousedown", this.onMouseDown.bind(e)); var c = t.container, h, p, d, v, m, g, y = 0, b, w, E, S, x; this.onDragStart = function (e) { if (this.cancelDrag || !c.draggable) { var r = this; return setTimeout(function () { r.startSelect(), r.captureMouse(e) }, 0), e.preventDefault() } m = t.getSelectionRange(); var i = e.dataTransfer; i.effectAllowed = t.getReadOnly() ? "copy" : "copyMove", s.isOpera && (t.container.appendChild(n), n.scrollTop = 0), i.setDragImage && i.setDragImage(n, 0, 0), s.isOpera && t.container.removeChild(n), i.clearData(), i.setData("Text", t.session.getTextRange()), w = !0, this.setState("drag") }, this.onDragEnd = function (e) { c.draggable = !1, w = !1, this.setState(null); if (!t.getReadOnly()) { var n = e.dataTransfer.dropEffect; !b && n == "move" && t.session.remove(t.getSelectionRange()), t.$resetCursorStyle() } this.editor.unsetStyle("ace_dragging"), this.editor.renderer.setCursorStyle("") }, this.onDragEnter = function (e) { if (t.getReadOnly() || !M(e.dataTransfer)) return; return p = e.clientX, d = e.clientY, h || k(), y++, e.dataTransfer.dropEffect = b = _(e), i.preventDefault(e) }, this.onDragOver = function (e) { if (t.getReadOnly() || !M(e.dataTransfer)) return; return p = e.clientX, d = e.clientY, h || (k(), y++), A !== null && (A = null), e.dataTransfer.dropEffect = b = _(e), i.preventDefault(e) }, this.onDragLeave = function (e) { y--; if (y <= 0 && h) return L(), b = null, i.preventDefault(e) }, this.onDrop = function (e) { if (!g) return; var n = e.dataTransfer; if (w) switch (b) { case "move": m.contains(g.row, g.column) ? m = { start: g, end: g } : m = t.moveText(m, g); break; case "copy": m = t.moveText(m, g, !0) } else { var r = n.getData("Text"); m = { start: g, end: t.session.insert(g, r) }, t.focus(), b = null } return L(), i.preventDefault(e) }, i.addListener(c, "dragstart", this.onDragStart.bind(e), t), i.addListener(c, "dragend", this.onDragEnd.bind(e), t), i.addListener(c, "dragenter", this.onDragEnter.bind(e), t), i.addListener(c, "dragover", this.onDragOver.bind(e), t), i.addListener(c, "dragleave", this.onDragLeave.bind(e), t), i.addListener(c, "drop", this.onDrop.bind(e), t); var A = null } function l(e, t, n, r) { return Math.sqrt(Math.pow(n - e, 2) + Math.pow(r - t, 2)) } var r = e("../lib/dom"), i = e("../lib/event"), s = e("../lib/useragent"), o = 200, u = 200, a = 5; (function () { this.dragWait = function () { var e = Date.now() - this.mousedownEvent.time; e > this.editor.getDragDelay() && this.startDrag() }, this.dragWaitEnd = function () { var e = this.editor.container; e.draggable = !1, this.startSelect(this.mousedownEvent.getDocumentPosition()), this.selectEnd() }, this.dragReadyEnd = function (e) { this.editor.$resetCursorStyle(), this.editor.unsetStyle("ace_dragging"), this.editor.renderer.setCursorStyle(""), this.dragWaitEnd() }, this.startDrag = function () { this.cancelDrag = !1; var e = this.editor, t = e.container; t.draggable = !0, e.renderer.$cursorLayer.setBlinking(!1), e.setStyle("ace_dragging"); var n = s.isWin ? "default" : "move"; e.renderer.setCursorStyle(n), this.setState("dragReady") }, this.onMouseDrag = function (e) { var t = this.editor.container; if (s.isIE && this.state == "dragReady") { var n = l(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); n > 3 && t.dragDrop() } if (this.state === "dragWait") { var n = l(this.mousedownEvent.x, this.mousedownEvent.y, this.x, this.y); n > 0 && (t.draggable = !1, this.startSelect(this.mousedownEvent.getDocumentPosition())) } }, this.onMouseDown = function (e) { if (!this.$dragEnabled) return; this.mousedownEvent = e; var t = this.editor, n = e.inSelection(), r = e.getButton(), i = e.domEvent.detail || 1; if (i === 1 && r === 0 && n) { if (e.editor.inMultiSelectMode && (e.getAccelKey() || e.getShiftKey())) return; this.mousedownEvent.time = Date.now(); var o = e.domEvent.target || e.domEvent.srcElement; "unselectable" in o && (o.unselectable = "on"); if (t.getDragDelay()) { if (s.isWebKit) { this.cancelDrag = !0; var u = t.container; u.draggable = !0 } this.setState("dragWait") } else this.startDrag(); this.captureMouse(e, this.onMouseDrag.bind(this)), e.defaultPrevented = !0 } } }).call(f.prototype), t.DragdropHandler = f }), define("ace/mouse/touch_handler", ["require", "exports", "module", "ace/mouse/mouse_event", "ace/lib/event", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("./mouse_event").MouseEvent, i = e("../lib/event"), s = e("../lib/dom"); t.addTouchListeners = function (e, t) { function b() { var e = window.navigator && window.navigator.clipboard, r = !1, i = function () { var n = t.getCopyText(), i = t.session.getUndoManager().hasUndo(); y.replaceChild(s.buildDom(r ? ["span", !n && ["span", { "class": "ace_mobile-button", action: "selectall" }, "Select All"], n && ["span", { "class": "ace_mobile-button", action: "copy" }, "Copy"], n && ["span", { "class": "ace_mobile-button", action: "cut" }, "Cut"], e && ["span", { "class": "ace_mobile-button", action: "paste" }, "Paste"], i && ["span", { "class": "ace_mobile-button", action: "undo" }, "Undo"], ["span", { "class": "ace_mobile-button", action: "find" }, "Find"], ["span", { "class": "ace_mobile-button", action: "openCommandPallete" }, "Pallete"]] : ["span"]), y.firstChild) }, o = function (n) { var s = n.target.getAttribute("action"); if (s == "more" || !r) return r = !r, i(); if (s == "paste") e.readText().then(function (e) { t.execCommand(s, e) }); else if (s) { if (s == "cut" || s == "copy") e ? e.writeText(t.getCopyText()) : document.execCommand("copy"); t.execCommand(s) } y.firstChild.style.display = "none", r = !1, s != "openCommandPallete" && t.focus() }; y = s.buildDom(["div", { "class": "ace_mobile-menu", ontouchstart: function (e) { n = "menu", e.stopPropagation(), e.preventDefault(), t.textInput.focus() }, ontouchend: function (e) { e.stopPropagation(), e.preventDefault(), o(e) }, onclick: o }, ["span"], ["span", { "class": "ace_mobile-button", action: "more" }, "..."]], t.container) } function w() { y || b(); var e = t.selection.cursor, n = t.renderer.textToScreenCoordinates(e.row, e.column), r = t.container.getBoundingClientRect(); y.style.top = n.pageY - r.top - 3 + "px", y.style.right = "10px", y.style.display = "", y.firstChild.style.display = "none", t.on("input", E) } function E(e) { y && (y.style.display = "none"), t.off("input", E) } function S() { l = null, clearTimeout(l); var e = t.selection.getRange(), r = e.contains(p.row, p.column); if (e.isEmpty() || !r) t.selection.moveToPosition(p), t.selection.selectWord(); n = "wait", w() } function x() { l = null, clearTimeout(l), t.selection.moveToPosition(p); var e = d >= 2 ? t.selection.getLineRange(p.row) : t.session.getBracketRange(p); e && !e.isEmpty() ? t.selection.setRange(e) : t.selection.selectWord(), n = "wait" } function T() { h += 60, c = setInterval(function () { h-- <= 0 && (clearInterval(c), c = null), Math.abs(v) < .01 && (v = 0), Math.abs(m) < .01 && (m = 0), h < 20 && (v = .9 * v), h < 20 && (m = .9 * m); var e = t.session.getScrollTop(); t.renderer.scrollBy(10 * v, 10 * m), e == t.session.getScrollTop() && (h = 0) }, 10) } var n = "scroll", o, u, a, f, l, c, h = 0, p, d = 0, v = 0, m = 0, g, y; i.addListener(e, "contextmenu", function (e) { if (!g) return; var n = t.textInput.getElement(); n.focus() }, t), i.addListener(e, "touchstart", function (e) { var i = e.touches; if (l || i.length > 1) { clearTimeout(l), l = null, a = -1, n = "zoom"; return } g = t.$mouseHandler.isMousePressed = !0; var s = t.renderer.layerConfig.lineHeight, c = t.renderer.layerConfig.lineHeight, y = e.timeStamp; f = y; var b = i[0], w = b.clientX, E = b.clientY; Math.abs(o - w) + Math.abs(u - E) > s && (a = -1), o = e.clientX = w, u = e.clientY = E, v = m = 0; var T = new r(e, t); p = T.getDocumentPosition(); if (y - a < 500 && i.length == 1 && !h) d++, e.preventDefault(), e.button = 0, x(); else { d = 0; var N = t.selection.cursor, C = t.selection.isEmpty() ? N : t.selection.anchor, k = t.renderer.$cursorLayer.getPixelPosition(N, !0), L = t.renderer.$cursorLayer.getPixelPosition(C, !0), A = t.renderer.scroller.getBoundingClientRect(), O = function (e, t) { return e /= c, t = t / s - .75, e * e + t * t }; if (e.clientX < A.left) { n = "zoom"; return } var M = O(e.clientX - A.left - k.left, e.clientY - A.top - k.top), _ = O(e.clientX - A.left - L.left, e.clientY - A.top - L.top); M < 3.5 && _ < 3.5 && (n = M > _ ? "cursor" : "anchor"), _ < 3.5 ? n = "anchor" : M < 3.5 ? n = "cursor" : n = "scroll", l = setTimeout(S, 450) } a = y }, t), i.addListener(e, "touchend", function (e) { g = t.$mouseHandler.isMousePressed = !1, c && clearInterval(c), n == "zoom" ? (n = "", h = 0) : l ? (t.selection.moveToPosition(p), h = 0, w()) : n == "scroll" ? (T(), E()) : w(), clearTimeout(l), l = null }, t), i.addListener(e, "touchmove", function (e) { l && (clearTimeout(l), l = null); var i = e.touches; if (i.length > 1 || n == "zoom") return; var s = i[0], a = o - s.clientX, c = u - s.clientY; if (n == "wait") { if (!(a * a + c * c > 4)) return e.preventDefault(); n = "cursor" } o = s.clientX, u = s.clientY, e.clientX = s.clientX, e.clientY = s.clientY; var h = e.timeStamp, p = h - f; f = h; if (n == "scroll") { var d = new r(e, t); d.speed = 1, d.wheelX = a, d.wheelY = c, 10 * Math.abs(a) < Math.abs(c) && (a = 0), 10 * Math.abs(c) < Math.abs(a) && (c = 0), p != 0 && (v = a / p, m = c / p), t._emit("mousewheel", d), d.propagationStopped || (v = m = 0) } else { var g = new r(e, t), y = g.getDocumentPosition(); n == "cursor" ? t.selection.moveCursorToPosition(y) : n == "anchor" && t.selection.setSelectionAnchor(y.row, y.column), t.renderer.scrollCursorIntoView(y), e.preventDefault() } }, t) } }), define("ace/lib/net", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("./dom"); t.get = function (e, t) { var n = new XMLHttpRequest; n.open("GET", e, !0), n.onreadystatechange = function () { n.readyState === 4 && t(n.responseText) }, n.send(null) }, t.loadScript = function (e, t) { var n = r.getDocumentHead(), i = document.createElement("script"); i.src = e, n.appendChild(i), i.onload = i.onreadystatechange = function (e, n) { if (n || !i.readyState || i.readyState == "loaded" || i.readyState == "complete") i = i.onload = i.onreadystatechange = null, n || t() } }, t.qualifyURL = function (e) { var t = document.createElement("a"); return t.href = e, t.href } }), define("ace/lib/event_emitter", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = {}, i = function () { this.propagationStopped = !0 }, s = function () { this.defaultPrevented = !0 }; r._emit = r._dispatchEvent = function (e, t) { this._eventRegistry || (this._eventRegistry = {}), this._defaultHandlers || (this._defaultHandlers = {}); var n = this._eventRegistry[e] || [], r = this._defaultHandlers[e]; if (!n.length && !r) return; if (typeof t != "object" || !t) t = {}; t.type || (t.type = e), t.stopPropagation || (t.stopPropagation = i), t.preventDefault || (t.preventDefault = s), n = n.slice(); for (var o = 0; o < n.length; o++) { n[o](t, this); if (t.propagationStopped) break } if (r && !t.defaultPrevented) return r(t, this) }, r._signal = function (e, t) { var n = (this._eventRegistry || {})[e]; if (!n) return; n = n.slice(); for (var r = 0; r < n.length; r++)n[r](t, this) }, r.once = function (e, t) { var n = this; this.on(e, function r() { n.off(e, r), t.apply(null, arguments) }); if (!t) return new Promise(function (e) { t = e }) }, r.setDefaultHandler = function (e, t) { var n = this._defaultHandlers; n || (n = this._defaultHandlers = { _disabled_: {} }); if (n[e]) { var r = n[e], i = n._disabled_[e]; i || (n._disabled_[e] = i = []), i.push(r); var s = i.indexOf(t); s != -1 && i.splice(s, 1) } n[e] = t }, r.removeDefaultHandler = function (e, t) { var n = this._defaultHandlers; if (!n) return; var r = n._disabled_[e]; if (n[e] == t) r && this.setDefaultHandler(e, r.pop()); else if (r) { var i = r.indexOf(t); i != -1 && r.splice(i, 1) } }, r.on = r.addEventListener = function (e, t, n) { this._eventRegistry = this._eventRegistry || {}; var r = this._eventRegistry[e]; return r || (r = this._eventRegistry[e] = []), r.indexOf(t) == -1 && r[n ? "unshift" : "push"](t), t }, r.off = r.removeListener = r.removeEventListener = function (e, t) { this._eventRegistry = this._eventRegistry || {}; var n = this._eventRegistry[e]; if (!n) return; var r = n.indexOf(t); r !== -1 && n.splice(r, 1) }, r.removeAllListeners = function (e) { e || (this._eventRegistry = this._defaultHandlers = undefined), this._eventRegistry && (this._eventRegistry[e] = undefined), this._defaultHandlers && (this._defaultHandlers[e] = undefined) }, t.EventEmitter = r }), define("ace/lib/app_config", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "no use strict"; function o(e) { typeof console != "undefined" && console.warn && console.warn.apply(console, arguments) } function u(e, t) { var n = new Error(e); n.data = t, typeof console == "object" && console.error && console.error(n), setTimeout(function () { throw n }) } var r = e("./oop"), i = e("./event_emitter").EventEmitter, s = { setOptions: function (e) { Object.keys(e).forEach(function (t) { this.setOption(t, e[t]) }, this) }, getOptions: function (e) { var t = {}; if (!e) { var n = this.$options; e = Object.keys(n).filter(function (e) { return !n[e].hidden }) } else Array.isArray(e) || (t = e, e = Object.keys(t)); return e.forEach(function (e) { t[e] = this.getOption(e) }, this), t }, setOption: function (e, t) { if (this["$" + e] === t) return; var n = this.$options[e]; if (!n) return o('misspelled option "' + e + '"'); if (n.forwardTo) return this[n.forwardTo] && this[n.forwardTo].setOption(e, t); n.handlesSet || (this["$" + e] = t), n && n.set && n.set.call(this, t) }, getOption: function (e) { var t = this.$options[e]; return t ? t.forwardTo ? this[t.forwardTo] && this[t.forwardTo].getOption(e) : t && t.get ? t.get.call(this) : this["$" + e] : o('misspelled option "' + e + '"') } }, a = function () { this.$defaultOptions = {} }; (function () { r.implement(this, i), this.defineOptions = function (e, t, n) { return e.$options || (this.$defaultOptions[t] = e.$options = {}), Object.keys(n).forEach(function (t) { var r = n[t]; typeof r == "string" && (r = { forwardTo: r }), r.name || (r.name = t), e.$options[r.name] = r, "initialValue" in r && (e["$" + r.name] = r.initialValue) }), r.implement(e, s), this }, this.resetOptions = function (e) { Object.keys(e.$options).forEach(function (t) { var n = e.$options[t]; "value" in n && e.setOption(t, n.value) }) }, this.setDefaultValue = function (e, t, n) { if (!e) { for (e in this.$defaultOptions) if (this.$defaultOptions[e][t]) break; if (!this.$defaultOptions[e][t]) return !1 } var r = this.$defaultOptions[e] || (this.$defaultOptions[e] = {}); r[t] && (r.forwardTo ? this.setDefaultValue(r.forwardTo, t, n) : r[t].value = n) }, this.setDefaultValues = function (e, t) { Object.keys(t).forEach(function (n) { this.setDefaultValue(e, n, t[n]) }, this) }, this.warn = o, this.reportError = u }).call(a.prototype), t.AppConfig = a }), define("ace/config", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/lib/net", "ace/lib/app_config"], function (e, t, n) { "no use strict"; function l(r) { if (!u || !u.document) return; a.packaged = r || e.packaged || n.packaged || u.define && define.packaged; var i = {}, s = "", o = document.currentScript || document._currentScript, f = o && o.ownerDocument || document, l = f.getElementsByTagName("script"); for (var h = 0; h < l.length; h++) { var p = l[h], d = p.src || p.getAttribute("src"); if (!d) continue; var v = p.attributes; for (var m = 0, g = v.length; m < g; m++) { var y = v[m]; y.name.indexOf("data-ace-") === 0 && (i[c(y.name.replace(/^data-ace-/, ""))] = y.value) } var b = d.match(/^(.*)\/ace(\-\w+)?\.js(\?|$)/); b && (s = b[1]) } s && (i.base = i.base || s, i.packaged = !0), i.basePath = i.base, i.workerPath = i.workerPath || i.base, i.modePath = i.modePath || i.base, i.themePath = i.themePath || i.base, delete i.base; for (var w in i) typeof i[w] != "undefined" && t.set(w, i[w]) } function c(e) { return e.replace(/-(.)/g, function (e, t) { return t.toUpperCase() }) } var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./lib/net"), o = e("./lib/app_config").AppConfig; n.exports = t = new o; var u = function () { return this || typeof window != "undefined" && window }(), a = { packaged: !1, workerPath: null, modePath: null, themePath: null, basePath: "", suffix: ".js", $moduleUrls: {}, loadWorkerFromBlob: !0, sharedPopups: !1 }; t.get = function (e) { if (!a.hasOwnProperty(e)) throw new Error("Unknown config key: " + e); return a[e] }, t.set = function (e, t) { if (a.hasOwnProperty(e)) a[e] = t; else if (this.setDefaultValue("", e, t) == 0) throw new Error("Unknown config key: " + e) }, t.all = function () { return r.copyObject(a) }, t.$modes = {}, t.moduleUrl = function (e, t) { if (a.$moduleUrls[e]) return a.$moduleUrls[e]; var n = e.split("/"); t = t || n[n.length - 2] || ""; var r = t == "snippets" ? "/" : "-", i = n[n.length - 1]; if (t == "worker" && r == "-") { var s = new RegExp("^" + t + "[\\-_]|[\\-_]" + t + "$", "g"); i = i.replace(s, "") } (!i || i == t) && n.length > 1 && (i = n[n.length - 2]); var o = a[t + "Path"]; return o == null ? o = a.basePath : r == "/" && (t = r = ""), o && o.slice(-1) != "/" && (o += "/"), o + t + r + i + this.get("suffix") }, t.setModuleUrl = function (e, t) { return a.$moduleUrls[e] = t }, t.$loading = {}, t.loadModule = function (n, r) { var i, o; Array.isArray(n) && (o = n[0], n = n[1]); try { i = e(n) } catch (u) { } if (i && !t.$loading[n]) return r && r(i); t.$loading[n] || (t.$loading[n] = []), t.$loading[n].push(r); if (t.$loading[n].length > 1) return; var a = function () { e([n], function (e) { t._emit("load.module", { name: n, module: e }); var r = t.$loading[n]; t.$loading[n] = null, r.forEach(function (t) { t && t(e) }) }) }; if (!t.get("packaged")) return a(); s.loadScript(t.moduleUrl(n, o), a), f() }; var f = function () { !a.basePath && !a.workerPath && !a.modePath && !a.themePath && !Object.keys(a.$moduleUrls).length && (console.error("Unable to infer path to ace from script src,", "use ace.config.set('basePath', 'path') to enable dynamic loading of modes and themes", "or with webpack use ace/webpack-resolver"), f = function () { }) }; t.init = l, t.version = "1.4.10" }), define("ace/mouse/mouse_handler", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent", "ace/mouse/default_handlers", "ace/mouse/default_gutter_handler", "ace/mouse/mouse_event", "ace/mouse/dragdrop_handler", "ace/mouse/touch_handler", "ace/config"], function (e, t, n) { "use strict"; var r = e("../lib/event"), i = e("../lib/useragent"), s = e("./default_handlers").DefaultHandlers, o = e("./default_gutter_handler").GutterHandler, u = e("./mouse_event").MouseEvent, a = e("./dragdrop_handler").DragdropHandler, f = e("./touch_handler").addTouchListeners, l = e("../config"), c = function (e) { var t = this; this.editor = e, new s(this), new o(this), new a(this); var n = function (t) { var n = !document.hasFocus || !document.hasFocus() || !e.isFocused() && document.activeElement == (e.textInput && e.textInput.getElement()); n && window.focus(), e.focus() }, u = e.renderer.getMouseEventTarget(); r.addListener(u, "click", this.onMouseEvent.bind(this, "click"), e), r.addListener(u, "mousemove", this.onMouseMove.bind(this, "mousemove"), e), r.addMultiMouseDownListener([u, e.renderer.scrollBarV && e.renderer.scrollBarV.inner, e.renderer.scrollBarH && e.renderer.scrollBarH.inner, e.textInput && e.textInput.getElement()].filter(Boolean), [400, 300, 250], this, "onMouseEvent", e), r.addMouseWheelListener(e.container, this.onMouseWheel.bind(this, "mousewheel"), e), f(e.container, e); var l = e.renderer.$gutter; r.addListener(l, "mousedown", this.onMouseEvent.bind(this, "guttermousedown"), e), r.addListener(l, "click", this.onMouseEvent.bind(this, "gutterclick"), e), r.addListener(l, "dblclick", this.onMouseEvent.bind(this, "gutterdblclick"), e), r.addListener(l, "mousemove", this.onMouseEvent.bind(this, "guttermousemove"), e), r.addListener(u, "mousedown", n, e), r.addListener(l, "mousedown", n, e), i.isIE && e.renderer.scrollBarV && (r.addListener(e.renderer.scrollBarV.element, "mousedown", n, e), r.addListener(e.renderer.scrollBarH.element, "mousedown", n, e)), e.on("mousemove", function (n) { if (t.state || t.$dragDelay || !t.$dragEnabled) return; var r = e.renderer.screenToTextCoordinates(n.x, n.y), i = e.session.selection.getRange(), s = e.renderer; !i.isEmpty() && i.insideStart(r.row, r.column) ? s.setCursorStyle("default") : s.setCursorStyle("") }, e) }; (function () { this.onMouseEvent = function (e, t) { this.editor._emit(e, new u(t, this.editor)) }, this.onMouseMove = function (e, t) { var n = this.editor._eventRegistry && this.editor._eventRegistry.mousemove; if (!n || !n.length) return; this.editor._emit(e, new u(t, this.editor)) }, this.onMouseWheel = function (e, t) { var n = new u(t, this.editor); n.speed = this.$scrollSpeed * 2, n.wheelX = t.wheelX, n.wheelY = t.wheelY, this.editor._emit(e, n) }, this.setState = function (e) { this.state = e }, this.captureMouse = function (e, t) { this.x = e.x, this.y = e.y, this.isMousePressed = !0; var n = this.editor, s = this.editor.renderer; s.$isMousePressed = !0; var o = this, a = function (e) { if (!e) return; if (i.isWebKit && !e.which && o.releaseMouse) return o.releaseMouse(); o.x = e.clientX, o.y = e.clientY, t && t(e), o.mouseEvent = new u(e, o.editor), o.$mouseMoved = !0 }, f = function (e) { n.off("beforeEndOperation", c), clearInterval(h), l(), o[o.state + "End"] && o[o.state + "End"](e), o.state = "", o.isMousePressed = s.$isMousePressed = !1, s.$keepTextAreaAtCursor && s.$moveTextAreaToCursor(), o.$onCaptureMouseMove = o.releaseMouse = null, e && o.onMouseEvent("mouseup", e), n.endOperation() }, l = function () { o[o.state] && o[o.state](), o.$mouseMoved = !1 }; if (i.isOldIE && e.domEvent.type == "dblclick") return setTimeout(function () { f(e) }); var c = function (e) { if (!o.releaseMouse) return; n.curOp.command.name && n.curOp.selectionChanged && (o[o.state + "End"] && o[o.state + "End"](), o.state = "", o.releaseMouse()) }; n.on("beforeEndOperation", c), n.startOperation({ command: { name: "mouse" } }), o.$onCaptureMouseMove = a, o.releaseMouse = r.capture(this.editor.container, a, f); var h = setInterval(l, 20) }, this.releaseMouse = null, this.cancelContextMenu = function () { var e = function (t) { if (t && t.domEvent && t.domEvent.type != "contextmenu") return; this.editor.off("nativecontextmenu", e), t && t.domEvent && r.stopEvent(t.domEvent) }.bind(this); setTimeout(e, 10), this.editor.on("nativecontextmenu", e) } }).call(c.prototype), l.defineOptions(c.prototype, "mouseHandler", { scrollSpeed: { initialValue: 2 }, dragDelay: { initialValue: i.isMac ? 150 : 0 }, dragEnabled: { initialValue: !0 }, focusTimeout: { initialValue: 0 }, tooltipFollowsMouse: { initialValue: !0 } }), t.MouseHandler = c }), define("ace/mouse/fold_handler", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; function i(e) { e.on("click", function (t) { var n = t.getDocumentPosition(), i = e.session, s = i.getFoldAt(n.row, n.column, 1); s && (t.getAccelKey() ? i.removeFold(s) : i.expandFold(s), t.stop()); var o = t.domEvent && t.domEvent.target; o && r.hasCssClass(o, "ace_inline_button") && r.hasCssClass(o, "ace_toggle_wrap") && (i.setOption("wrap", !i.getUseWrapMode()), e.renderer.scrollCursorIntoView()) }), e.on("gutterclick", function (t) { var n = e.renderer.$gutterLayer.getRegion(t); if (n == "foldWidgets") { var r = t.getDocumentPosition().row, i = e.session; i.foldWidgets && i.foldWidgets[r] && e.session.onFoldWidgetClick(r, t), e.isFocused() || e.focus(), t.stop() } }), e.on("gutterdblclick", function (t) { var n = e.renderer.$gutterLayer.getRegion(t); if (n == "foldWidgets") { var r = t.getDocumentPosition().row, i = e.session, s = i.getParentFoldRangeData(r, !0), o = s.range || s.firstRange; if (o) { r = o.start.row; var u = i.getFoldAt(r, i.getLine(r).length, 1); u ? i.removeFold(u) : (i.addFold("...", o), e.renderer.scrollCursorIntoView({ row: o.start.row, column: 0 })) } t.stop() } }) } var r = e("../lib/dom"); t.FoldHandler = i }), define("ace/keyboard/keybinding", ["require", "exports", "module", "ace/lib/keys", "ace/lib/event"], function (e, t, n) { "use strict"; var r = e("../lib/keys"), i = e("../lib/event"), s = function (e) { this.$editor = e, this.$data = { editor: e }, this.$handlers = [], this.setDefaultHandler(e.commands) }; (function () { this.setDefaultHandler = function (e) { this.removeKeyboardHandler(this.$defaultHandler), this.$defaultHandler = e, this.addKeyboardHandler(e, 0) }, this.setKeyboardHandler = function (e) { var t = this.$handlers; if (t[t.length - 1] == e) return; while (t[t.length - 1] && t[t.length - 1] != this.$defaultHandler) this.removeKeyboardHandler(t[t.length - 1]); this.addKeyboardHandler(e, 1) }, this.addKeyboardHandler = function (e, t) { if (!e) return; typeof e == "function" && !e.handleKeyboard && (e.handleKeyboard = e); var n = this.$handlers.indexOf(e); n != -1 && this.$handlers.splice(n, 1), t == undefined ? this.$handlers.push(e) : this.$handlers.splice(t, 0, e), n == -1 && e.attach && e.attach(this.$editor) }, this.removeKeyboardHandler = function (e) { var t = this.$handlers.indexOf(e); return t == -1 ? !1 : (this.$handlers.splice(t, 1), e.detach && e.detach(this.$editor), !0) }, this.getKeyboardHandler = function () { return this.$handlers[this.$handlers.length - 1] }, this.getStatusText = function () { var e = this.$data, t = e.editor; return this.$handlers.map(function (n) { return n.getStatusText && n.getStatusText(t, e) || "" }).filter(Boolean).join(" ") }, this.$callKeyboardHandlers = function (e, t, n, r) { var s, o = !1, u = this.$editor.commands; for (var a = this.$handlers.length; a--;) { s = this.$handlers[a].handleKeyboard(this.$data, e, t, n, r); if (!s || !s.command) continue; s.command == "null" ? o = !0 : o = u.exec(s.command, this.$editor, s.args, r), o && r && e != -1 && s.passEvent != 1 && s.command.passEvent != 1 && i.stopEvent(r); if (o) break } return !o && e == -1 && (s = { command: "insertstring" }, o = u.exec("insertstring", this.$editor, t)), o && this.$editor._signal && this.$editor._signal("keyboardActivity", s), o }, this.onCommandKey = function (e, t, n) { var i = r.keyCodeToString(n); return this.$callKeyboardHandlers(t, i, n, e) }, this.onTextInput = function (e) { return this.$callKeyboardHandlers(-1, e) } }).call(s.prototype), t.KeyBinding = s }), define("ace/lib/bidiutil", ["require", "exports", "module"], function (e, t, n) { "use strict"; function F(e, t, n, r) { var i = s ? d : p, c = null, h = null, v = null, m = 0, g = null, y = null, b = -1, w = null, E = null, T = []; if (!r) for (w = 0, r = []; w < n; w++)r[w] = R(e[w]); o = s, u = !1, a = !1, f = !1, l = !1; for (E = 0; E < n; E++) { c = m, T[E] = h = q(e, r, T, E), m = i[c][h], g = m & 240, m &= 15, t[E] = v = i[m][5]; if (g > 0) if (g == 16) { for (w = b; w < E; w++)t[w] = 1; b = -1 } else b = -1; y = i[m][6]; if (y) b == -1 && (b = E); else if (b > -1) { for (w = b; w < E; w++)t[w] = v; b = -1 } r[E] == S && (t[E] = 0), o |= v } if (l) for (w = 0; w < n; w++)if (r[w] == x) { t[w] = s; for (var C = w - 1; C >= 0; C--) { if (r[C] != N) break; t[C] = s } } } function I(e, t, n) { if (o < e) return; if (e == 1 && s == m && !f) { n.reverse(); return } var r = n.length, i = 0, u, a, l, c; while (i < r) { if (t[i] >= e) { u = i + 1; while (u < r && t[u] >= e) u++; for (a = i, l = u - 1; a < l; a++, l--)c = n[a], n[a] = n[l], n[l] = c; i = u } i++ } } function q(e, t, n, r) { var i = t[r], o, c, h, p; switch (i) { case g: case y: u = !1; case E: case w: return i; case b: return u ? w : b; case T: return u = !0, a = !0, y; case N: return E; case C: if (r < 1 || r + 1 >= t.length || (o = n[r - 1]) != b && o != w || (c = t[r + 1]) != b && c != w) return E; return u && (c = w), c == o ? c : E; case k: o = r > 0 ? n[r - 1] : S; if (o == b && r + 1 < t.length && t[r + 1] == b) return b; return E; case L: if (r > 0 && n[r - 1] == b) return b; if (u) return E; p = r + 1, h = t.length; while (p < h && t[p] == L) p++; if (p < h && t[p] == b) return b; return E; case A: h = t.length, p = r + 1; while (p < h && t[p] == A) p++; if (p < h) { var d = e[r], v = d >= 1425 && d <= 2303 || d == 64286; o = t[p]; if (v && (o == y || o == T)) return y } if (r < 1 || (o = t[r - 1]) == S) return E; return n[r - 1]; case S: return u = !1, f = !0, s; case x: return l = !0, E; case O: case M: case D: case P: case _: u = !1; case H: return E } } function R(e) { var t = e.charCodeAt(0), n = t >> 8; return n == 0 ? t > 191 ? g : B[t] : n == 5 ? /[\u0591-\u05f4]/.test(e) ? y : g : n == 6 ? /[\u0610-\u061a\u064b-\u065f\u06d6-\u06e4\u06e7-\u06ed]/.test(e) ? A : /[\u0660-\u0669\u066b-\u066c]/.test(e) ? w : t == 1642 ? L : /[\u06f0-\u06f9]/.test(e) ? b : T : n == 32 && t <= 8287 ? j[t & 255] : n == 254 ? t >= 65136 ? T : E : E } function U(e) { return e >= "\u064b" && e <= "\u0655" } var r = ["\u0621", "\u0641"], i = ["\u063a", "\u064a"], s = 0, o = 0, u = !1, a = !1, f = !1, l = !1, c = !1, h = !1, p = [[0, 3, 0, 1, 0, 0, 0], [0, 3, 0, 1, 2, 2, 0], [0, 3, 0, 17, 2, 0, 1], [0, 3, 5, 5, 4, 1, 0], [0, 3, 21, 21, 4, 0, 1], [0, 3, 5, 5, 4, 2, 0]], d = [[2, 0, 1, 1, 0, 1, 0], [2, 0, 1, 1, 0, 2, 0], [2, 0, 2, 1, 3, 2, 0], [2, 0, 2, 33, 3, 1, 1]], v = 0, m = 1, g = 0, y = 1, b = 2, w = 3, E = 4, S = 5, x = 6, T = 7, N = 8, C = 9, k = 10, L = 11, A = 12, O = 13, M = 14, _ = 15, D = 16, P = 17, H = 18, B = [H, H, H, H, H, H, H, H, H, x, S, x, N, S, H, H, H, H, H, H, H, H, H, H, H, H, H, H, S, S, S, x, N, E, E, L, L, L, E, E, E, E, E, k, C, k, C, C, b, b, b, b, b, b, b, b, b, b, C, E, E, E, E, E, E, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, E, E, E, E, E, E, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, g, E, E, E, E, H, H, H, H, H, H, S, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, C, E, L, L, L, L, E, E, E, E, g, E, E, H, E, E, L, L, b, b, E, g, E, E, E, b, g, E, E, E, E, E], j = [N, N, N, N, N, N, N, N, N, N, N, H, H, H, g, y, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, N, S, O, M, _, D, P, C, L, L, L, L, L, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, C, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, N]; t.L = g, t.R = y, t.EN = b, t.ON_R = 3, t.AN = 4, t.R_H = 5, t.B = 6, t.RLE = 7, t.DOT = "\u00b7", t.doBidiReorder = function (e, n, r) { if (e.length < 2) return {}; var i = e.split(""), o = new Array(i.length), u = new Array(i.length), a = []; s = r ? m : v, F(i, a, i.length, n); for (var f = 0; f < o.length; o[f] = f, f++); I(2, a, o), I(1, a, o); for (var f = 0; f < o.length - 1; f++)n[f] === w ? a[f] = t.AN : a[f] === y && (n[f] > T && n[f] < O || n[f] === E || n[f] === H) ? a[f] = t.ON_R : f > 0 && i[f - 1] === "\u0644" && /\u0622|\u0623|\u0625|\u0627/.test(i[f]) && (a[f - 1] = a[f] = t.R_H, f++); i[i.length - 1] === t.DOT && (a[i.length - 1] = t.B), i[0] === "\u202b" && (a[0] = t.RLE); for (var f = 0; f < o.length; f++)u[f] = a[o[f]]; return { logicalFromVisual: o, bidiLevels: u } }, t.hasBidiCharacters = function (e, t) { var n = !1; for (var r = 0; r < e.length; r++)t[r] = R(e.charAt(r)), !n && (t[r] == y || t[r] == T || t[r] == w) && (n = !0); return n }, t.getVisualFromLogicalIdx = function (e, t) { for (var n = 0; n < t.logicalFromVisual.length; n++)if (t.logicalFromVisual[n] == e) return n; return 0 } }), define("ace/bidihandler", ["require", "exports", "module", "ace/lib/bidiutil", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("./lib/bidiutil"), i = e("./lib/lang"), s = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac\u202B]/, o = function (e) { this.session = e, this.bidiMap = {}, this.currentRow = null, this.bidiUtil = r, this.charWidths = [], this.EOL = "\u00ac", this.showInvisibles = !0, this.isRtlDir = !1, this.$isRtl = !1, this.line = "", this.wrapIndent = 0, this.EOF = "\u00b6", this.RLE = "\u202b", this.contentWidth = 0, this.fontMetrics = null, this.rtlLineOffset = 0, this.wrapOffset = 0, this.isMoveLeftOperation = !1, this.seenBidi = s.test(e.getValue()) }; (function () { this.isBidiRow = function (e, t, n) { return this.seenBidi ? (e !== this.currentRow && (this.currentRow = e, this.updateRowLine(t, n), this.updateBidiMap()), this.bidiMap.bidiLevels) : !1 }, this.onChange = function (e) { this.seenBidi ? this.currentRow = null : e.action == "insert" && s.test(e.lines.join("\n")) && (this.seenBidi = !0, this.currentRow = null) }, this.getDocumentRow = function () { var e = 0, t = this.session.$screenRowCache; if (t.length) { var n = this.session.$getRowCacheIndex(t, this.currentRow); n >= 0 && (e = this.session.$docRowCache[n]) } return e }, this.getSplitIndex = function () { var e = 0, t = this.session.$screenRowCache; if (t.length) { var n, r = this.session.$getRowCacheIndex(t, this.currentRow); while (this.currentRow - e > 0) { n = this.session.$getRowCacheIndex(t, this.currentRow - e - 1); if (n !== r) break; r = n, e++ } } else e = this.currentRow; return e }, this.updateRowLine = function (e, t) { e === undefined && (e = this.getDocumentRow()); var n = e === this.session.getLength() - 1, s = n ? this.EOF : this.EOL; this.wrapIndent = 0, this.line = this.session.getLine(e), this.isRtlDir = this.$isRtl || this.line.charAt(0) === this.RLE; if (this.session.$useWrapMode) { var o = this.session.$wrapData[e]; o && (t === undefined && (t = this.getSplitIndex()), t > 0 && o.length ? (this.wrapIndent = o.indent, this.wrapOffset = this.wrapIndent * this.charWidths[r.L], this.line = t < o.length ? this.line.substring(o[t - 1], o[t]) : this.line.substring(o[o.length - 1])) : this.line = this.line.substring(0, o[t])), t == o.length && (this.line += this.showInvisibles ? s : r.DOT) } else this.line += this.showInvisibles ? s : r.DOT; var u = this.session, a = 0, f; this.line = this.line.replace(/\t|[\u1100-\u2029, \u202F-\uFFE6]/g, function (e, t) { return e === " " || u.isFullWidth(e.charCodeAt(0)) ? (f = e === " " ? u.getScreenTabSize(t + a) : 2, a += f - 1, i.stringRepeat(r.DOT, f)) : e }), this.isRtlDir && (this.fontMetrics.$main.textContent = this.line.charAt(this.line.length - 1) == r.DOT ? this.line.substr(0, this.line.length - 1) : this.line, this.rtlLineOffset = this.contentWidth - this.fontMetrics.$main.getBoundingClientRect().width) }, this.updateBidiMap = function () { var e = []; r.hasBidiCharacters(this.line, e) || this.isRtlDir ? this.bidiMap = r.doBidiReorder(this.line, e, this.isRtlDir) : this.bidiMap = {} }, this.markAsDirty = function () { this.currentRow = null }, this.updateCharacterWidths = function (e) { if (this.characterWidth === e.$characterSize.width) return; this.fontMetrics = e; var t = this.characterWidth = e.$characterSize.width, n = e.$measureCharWidth("\u05d4"); this.charWidths[r.L] = this.charWidths[r.EN] = this.charWidths[r.ON_R] = t, this.charWidths[r.R] = this.charWidths[r.AN] = n, this.charWidths[r.R_H] = n * .45, this.charWidths[r.B] = this.charWidths[r.RLE] = 0, this.currentRow = null }, this.setShowInvisibles = function (e) { this.showInvisibles = e, this.currentRow = null }, this.setEolChar = function (e) { this.EOL = e }, this.setContentWidth = function (e) { this.contentWidth = e }, this.isRtlLine = function (e) { return this.$isRtl ? !0 : e != undefined ? this.session.getLine(e).charAt(0) == this.RLE : this.isRtlDir }, this.setRtlDirection = function (e, t) { var n = e.getCursorPosition(); for (var r = e.selection.getSelectionAnchor().row; r <= n.row; r++)!t && e.session.getLine(r).charAt(0) === e.session.$bidiHandler.RLE ? e.session.doc.removeInLine(r, 0, 1) : t && e.session.getLine(r).charAt(0) !== e.session.$bidiHandler.RLE && e.session.doc.insert({ column: 0, row: r }, e.session.$bidiHandler.RLE) }, this.getPosLeft = function (e) { e -= this.wrapIndent; var t = this.line.charAt(0) === this.RLE ? 1 : 0, n = e > t ? this.session.getOverwrite() ? e : e - 1 : t, i = r.getVisualFromLogicalIdx(n, this.bidiMap), s = this.bidiMap.bidiLevels, o = 0; !this.session.getOverwrite() && e <= t && s[i] % 2 !== 0 && i++; for (var u = 0; u < i; u++)o += this.charWidths[s[u]]; return !this.session.getOverwrite() && e > t && s[i] % 2 === 0 && (o += this.charWidths[s[i]]), this.wrapIndent && (o += this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset), this.isRtlDir && (o += this.rtlLineOffset), o }, this.getSelections = function (e, t) { var n = this.bidiMap, r = n.bidiLevels, i, s = [], o = 0, u = Math.min(e, t) - this.wrapIndent, a = Math.max(e, t) - this.wrapIndent, f = !1, l = !1, c = 0; this.wrapIndent && (o += this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset); for (var h, p = 0; p < r.length; p++)h = n.logicalFromVisual[p], i = r[p], f = h >= u && h < a, f && !l ? c = o : !f && l && s.push({ left: c, width: o - c }), o += this.charWidths[i], l = f; f && p === r.length && s.push({ left: c, width: o - c }); if (this.isRtlDir) for (var d = 0; d < s.length; d++)s[d].left += this.rtlLineOffset; return s }, this.offsetToCol = function (e) { this.isRtlDir && (e -= this.rtlLineOffset); var t = 0, e = Math.max(e, 0), n = 0, r = 0, i = this.bidiMap.bidiLevels, s = this.charWidths[i[r]]; this.wrapIndent && (e -= this.isRtlDir ? -1 * this.wrapOffset : this.wrapOffset); while (e > n + s / 2) { n += s; if (r === i.length - 1) { s = 0; break } s = this.charWidths[i[++r]] } return r > 0 && i[r - 1] % 2 !== 0 && i[r] % 2 === 0 ? (e < n && r--, t = this.bidiMap.logicalFromVisual[r]) : r > 0 && i[r - 1] % 2 === 0 && i[r] % 2 !== 0 ? t = 1 + (e > n ? this.bidiMap.logicalFromVisual[r] : this.bidiMap.logicalFromVisual[r - 1]) : this.isRtlDir && r === i.length - 1 && s === 0 && i[r - 1] % 2 === 0 || !this.isRtlDir && r === 0 && i[r] % 2 !== 0 ? t = 1 + this.bidiMap.logicalFromVisual[r] : (r > 0 && i[r - 1] % 2 !== 0 && s !== 0 && r--, t = this.bidiMap.logicalFromVisual[r]), t === 0 && this.isRtlDir && t++, t + this.wrapIndent } }).call(o.prototype), t.BidiHandler = o }), define("ace/selection", ["require", "exports", "module", "ace/lib/oop", "ace/lib/lang", "ace/lib/event_emitter", "ace/range"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/lang"), s = e("./lib/event_emitter").EventEmitter, o = e("./range").Range, u = function (e) { this.session = e, this.doc = e.getDocument(), this.clearSelection(), this.cursor = this.lead = this.doc.createAnchor(0, 0), this.anchor = this.doc.createAnchor(0, 0), this.$silent = !1; var t = this; this.cursor.on("change", function (e) { t.$cursorChanged = !0, t.$silent || t._emit("changeCursor"), !t.$isEmpty && !t.$silent && t._emit("changeSelection"), !t.$keepDesiredColumnOnChange && e.old.column != e.value.column && (t.$desiredColumn = null) }), this.anchor.on("change", function () { t.$anchorChanged = !0, !t.$isEmpty && !t.$silent && t._emit("changeSelection") }) }; (function () { r.implement(this, s), this.isEmpty = function () { return this.$isEmpty || this.anchor.row == this.lead.row && this.anchor.column == this.lead.column }, this.isMultiLine = function () { return !this.$isEmpty && this.anchor.row != this.cursor.row }, this.getCursor = function () { return this.lead.getPosition() }, this.setSelectionAnchor = function (e, t) { this.$isEmpty = !1, this.anchor.setPosition(e, t) }, this.getAnchor = this.getSelectionAnchor = function () { return this.$isEmpty ? this.getSelectionLead() : this.anchor.getPosition() }, this.getSelectionLead = function () { return this.lead.getPosition() }, this.isBackwards = function () { var e = this.anchor, t = this.lead; return e.row > t.row || e.row == t.row && e.column > t.column }, this.getRange = function () { var e = this.anchor, t = this.lead; return this.$isEmpty ? o.fromPoints(t, t) : this.isBackwards() ? o.fromPoints(t, e) : o.fromPoints(e, t) }, this.clearSelection = function () { this.$isEmpty || (this.$isEmpty = !0, this._emit("changeSelection")) }, this.selectAll = function () { this.$setSelection(0, 0, Number.MAX_VALUE, Number.MAX_VALUE) }, this.setRange = this.setSelectionRange = function (e, t) { var n = t ? e.end : e.start, r = t ? e.start : e.end; this.$setSelection(n.row, n.column, r.row, r.column) }, this.$setSelection = function (e, t, n, r) { if (this.$silent) return; var i = this.$isEmpty, s = this.inMultiSelectMode; this.$silent = !0, this.$cursorChanged = this.$anchorChanged = !1, this.anchor.setPosition(e, t), this.cursor.setPosition(n, r), this.$isEmpty = !o.comparePoints(this.anchor, this.cursor), this.$silent = !1, this.$cursorChanged && this._emit("changeCursor"), (this.$cursorChanged || this.$anchorChanged || i != this.$isEmpty || s) && this._emit("changeSelection") }, this.$moveSelection = function (e) { var t = this.lead; this.$isEmpty && this.setSelectionAnchor(t.row, t.column), e.call(this) }, this.selectTo = function (e, t) { this.$moveSelection(function () { this.moveCursorTo(e, t) }) }, this.selectToPosition = function (e) { this.$moveSelection(function () { this.moveCursorToPosition(e) }) }, this.moveTo = function (e, t) { this.clearSelection(), this.moveCursorTo(e, t) }, this.moveToPosition = function (e) { this.clearSelection(), this.moveCursorToPosition(e) }, this.selectUp = function () { this.$moveSelection(this.moveCursorUp) }, this.selectDown = function () { this.$moveSelection(this.moveCursorDown) }, this.selectRight = function () { this.$moveSelection(this.moveCursorRight) }, this.selectLeft = function () { this.$moveSelection(this.moveCursorLeft) }, this.selectLineStart = function () { this.$moveSelection(this.moveCursorLineStart) }, this.selectLineEnd = function () { this.$moveSelection(this.moveCursorLineEnd) }, this.selectFileEnd = function () { this.$moveSelection(this.moveCursorFileEnd) }, this.selectFileStart = function () { this.$moveSelection(this.moveCursorFileStart) }, this.selectWordRight = function () { this.$moveSelection(this.moveCursorWordRight) }, this.selectWordLeft = function () { this.$moveSelection(this.moveCursorWordLeft) }, this.getWordRange = function (e, t) { if (typeof t == "undefined") { var n = e || this.lead; e = n.row, t = n.column } return this.session.getWordRange(e, t) }, this.selectWord = function () { this.setSelectionRange(this.getWordRange()) }, this.selectAWord = function () { var e = this.getCursor(), t = this.session.getAWordRange(e.row, e.column); this.setSelectionRange(t) }, this.getLineRange = function (e, t) { var n = typeof e == "number" ? e : this.lead.row, r, i = this.session.getFoldLine(n); return i ? (n = i.start.row, r = i.end.row) : r = n, t === !0 ? new o(n, 0, r, this.session.getLine(r).length) : new o(n, 0, r + 1, 0) }, this.selectLine = function () { this.setSelectionRange(this.getLineRange()) }, this.moveCursorUp = function () { this.moveCursorBy(-1, 0) }, this.moveCursorDown = function () { this.moveCursorBy(1, 0) }, this.wouldMoveIntoSoftTab = function (e, t, n) { var r = e.column, i = e.column + t; return n < 0 && (r = e.column - t, i = e.column), this.session.isTabStop(e) && this.doc.getLine(e.row).slice(r, i).split(" ").length - 1 == t }, this.moveCursorLeft = function () { var e = this.lead.getPosition(), t; if (t = this.session.getFoldAt(e.row, e.column, -1)) this.moveCursorTo(t.start.row, t.start.column); else if (e.column === 0) e.row > 0 && this.moveCursorTo(e.row - 1, this.doc.getLine(e.row - 1).length); else { var n = this.session.getTabSize(); this.wouldMoveIntoSoftTab(e, n, -1) && !this.session.getNavigateWithinSoftTabs() ? this.moveCursorBy(0, -n) : this.moveCursorBy(0, -1) } }, this.moveCursorRight = function () { var e = this.lead.getPosition(), t; if (t = this.session.getFoldAt(e.row, e.column, 1)) this.moveCursorTo(t.end.row, t.end.column); else if (this.lead.column == this.doc.getLine(this.lead.row).length) this.lead.row < this.doc.getLength() - 1 && this.moveCursorTo(this.lead.row + 1, 0); else { var n = this.session.getTabSize(), e = this.lead; this.wouldMoveIntoSoftTab(e, n, 1) && !this.session.getNavigateWithinSoftTabs() ? this.moveCursorBy(0, n) : this.moveCursorBy(0, 1) } }, this.moveCursorLineStart = function () { var e = this.lead.row, t = this.lead.column, n = this.session.documentToScreenRow(e, t), r = this.session.screenToDocumentPosition(n, 0), i = this.session.getDisplayLine(e, null, r.row, r.column), s = i.match(/^\s*/); s[0].length != t && !this.session.$useEmacsStyleLineStart && (r.column += s[0].length), this.moveCursorToPosition(r) }, this.moveCursorLineEnd = function () { var e = this.lead, t = this.session.getDocumentLastRowColumnPosition(e.row, e.column); if (this.lead.column == t.column) { var n = this.session.getLine(t.row); if (t.column == n.length) { var r = n.search(/\s+$/); r > 0 && (t.column = r) } } this.moveCursorTo(t.row, t.column) }, this.moveCursorFileEnd = function () { var e = this.doc.getLength() - 1, t = this.doc.getLine(e).length; this.moveCursorTo(e, t) }, this.moveCursorFileStart = function () { this.moveCursorTo(0, 0) }, this.moveCursorLongWordRight = function () { var e = this.lead.row, t = this.lead.column, n = this.doc.getLine(e), r = n.substring(t); this.session.nonTokenRe.lastIndex = 0, this.session.tokenRe.lastIndex = 0; var i = this.session.getFoldAt(e, t, 1); if (i) { this.moveCursorTo(i.end.row, i.end.column); return } this.session.nonTokenRe.exec(r) && (t += this.session.nonTokenRe.lastIndex, this.session.nonTokenRe.lastIndex = 0, r = n.substring(t)); if (t >= n.length) { this.moveCursorTo(e, n.length), this.moveCursorRight(), e < this.doc.getLength() - 1 && this.moveCursorWordRight(); return } this.session.tokenRe.exec(r) && (t += this.session.tokenRe.lastIndex, this.session.tokenRe.lastIndex = 0), this.moveCursorTo(e, t) }, this.moveCursorLongWordLeft = function () { var e = this.lead.row, t = this.lead.column, n; if (n = this.session.getFoldAt(e, t, -1)) { this.moveCursorTo(n.start.row, n.start.column); return } var r = this.session.getFoldStringAt(e, t, -1); r == null && (r = this.doc.getLine(e).substring(0, t)); var s = i.stringReverse(r); this.session.nonTokenRe.lastIndex = 0, this.session.tokenRe.lastIndex = 0, this.session.nonTokenRe.exec(s) && (t -= this.session.nonTokenRe.lastIndex, s = s.slice(this.session.nonTokenRe.lastIndex), this.session.nonTokenRe.lastIndex = 0); if (t <= 0) { this.moveCursorTo(e, 0), this.moveCursorLeft(), e > 0 && this.moveCursorWordLeft(); return } this.session.tokenRe.exec(s) && (t -= this.session.tokenRe.lastIndex, this.session.tokenRe.lastIndex = 0), this.moveCursorTo(e, t) }, this.$shortWordEndIndex = function (e) { var t = 0, n, r = /\s/, i = this.session.tokenRe; i.lastIndex = 0; if (this.session.tokenRe.exec(e)) t = this.session.tokenRe.lastIndex; else { while ((n = e[t]) && r.test(n)) t++; if (t < 1) { i.lastIndex = 0; while ((n = e[t]) && !i.test(n)) { i.lastIndex = 0, t++; if (r.test(n)) { if (t > 2) { t--; break } while ((n = e[t]) && r.test(n)) t++; if (t > 2) break } } } } return i.lastIndex = 0, t }, this.moveCursorShortWordRight = function () { var e = this.lead.row, t = this.lead.column, n = this.doc.getLine(e), r = n.substring(t), i = this.session.getFoldAt(e, t, 1); if (i) return this.moveCursorTo(i.end.row, i.end.column); if (t == n.length) { var s = this.doc.getLength(); do e++, r = this.doc.getLine(e); while (e < s && /^\s*$/.test(r)); /^\s+/.test(r) || (r = ""), t = 0 } var o = this.$shortWordEndIndex(r); this.moveCursorTo(e, t + o) }, this.moveCursorShortWordLeft = function () { var e = this.lead.row, t = this.lead.column, n; if (n = this.session.getFoldAt(e, t, -1)) return this.moveCursorTo(n.start.row, n.start.column); var r = this.session.getLine(e).substring(0, t); if (t === 0) { do e--, r = this.doc.getLine(e); while (e > 0 && /^\s*$/.test(r)); t = r.length, /\s+$/.test(r) || (r = "") } var s = i.stringReverse(r), o = this.$shortWordEndIndex(s); return this.moveCursorTo(e, t - o) }, this.moveCursorWordRight = function () { this.session.$selectLongWords ? this.moveCursorLongWordRight() : this.moveCursorShortWordRight() }, this.moveCursorWordLeft = function () { this.session.$selectLongWords ? this.moveCursorLongWordLeft() : this.moveCursorShortWordLeft() }, this.moveCursorBy = function (e, t) { var n = this.session.documentToScreenPosition(this.lead.row, this.lead.column), r; t === 0 && (e !== 0 && (this.session.$bidiHandler.isBidiRow(n.row, this.lead.row) ? (r = this.session.$bidiHandler.getPosLeft(n.column), n.column = Math.round(r / this.session.$bidiHandler.charWidths[0])) : r = n.column * this.session.$bidiHandler.charWidths[0]), this.$desiredColumn ? n.column = this.$desiredColumn : this.$desiredColumn = n.column); if (e != 0 && this.session.lineWidgets && this.session.lineWidgets[this.lead.row]) { var i = this.session.lineWidgets[this.lead.row]; e < 0 ? e -= i.rowsAbove || 0 : e > 0 && (e += i.rowCount - (i.rowsAbove || 0)) } var s = this.session.screenToDocumentPosition(n.row + e, n.column, r); e !== 0 && t === 0 && s.row === this.lead.row && s.column === this.lead.column, this.moveCursorTo(s.row, s.column + t, t === 0) }, this.moveCursorToPosition = function (e) { this.moveCursorTo(e.row, e.column) }, this.moveCursorTo = function (e, t, n) { var r = this.session.getFoldAt(e, t, 1); r && (e = r.start.row, t = r.start.column), this.$keepDesiredColumnOnChange = !0; var i = this.session.getLine(e); /[\uDC00-\uDFFF]/.test(i.charAt(t)) && i.charAt(t - 1) && (this.lead.row == e && this.lead.column == t + 1 ? t -= 1 : t += 1), this.lead.setPosition(e, t), this.$keepDesiredColumnOnChange = !1, n || (this.$desiredColumn = null) }, this.moveCursorToScreen = function (e, t, n) { var r = this.session.screenToDocumentPosition(e, t); this.moveCursorTo(r.row, r.column, n) }, this.detach = function () { this.lead.detach(), this.anchor.detach(), this.session = this.doc = null }, this.fromOrientedRange = function (e) { this.setSelectionRange(e, e.cursor == e.start), this.$desiredColumn = e.desiredColumn || this.$desiredColumn }, this.toOrientedRange = function (e) { var t = this.getRange(); return e ? (e.start.column = t.start.column, e.start.row = t.start.row, e.end.column = t.end.column, e.end.row = t.end.row) : e = t, e.cursor = this.isBackwards() ? e.start : e.end, e.desiredColumn = this.$desiredColumn, e }, this.getRangeOfMovements = function (e) { var t = this.getCursor(); try { e(this); var n = this.getCursor(); return o.fromPoints(t, n) } catch (r) { return o.fromPoints(t, t) } finally { this.moveCursorToPosition(t) } }, this.toJSON = function () { if (this.rangeCount) var e = this.ranges.map(function (e) { var t = e.clone(); return t.isBackwards = e.cursor == e.start, t }); else { var e = this.getRange(); e.isBackwards = this.isBackwards() } return e }, this.fromJSON = function (e) { if (e.start == undefined) { if (this.rangeList && e.length > 1) { this.toSingleRange(e[0]); for (var t = e.length; t--;) { var n = o.fromPoints(e[t].start, e[t].end); e[t].isBackwards && (n.cursor = n.start), this.addRange(n, !0) } return } e = e[0] } this.rangeList && this.toSingleRange(e), this.setSelectionRange(e, e.isBackwards) }, this.isEqual = function (e) { if ((e.length || this.rangeCount) && e.length != this.rangeCount) return !1; if (!e.length || !this.ranges) return this.getRange().isEqual(e); for (var t = this.ranges.length; t--;)if (!this.ranges[t].isEqual(e[t])) return !1; return !0 } }).call(u.prototype), t.Selection = u }), define("ace/tokenizer", ["require", "exports", "module", "ace/config"], function (e, t, n) { "use strict"; var r = e("./config"), i = 2e3, s = function (e) { this.states = e, this.regExps = {}, this.matchMappings = {}; for (var t in this.states) { var n = this.states[t], r = [], i = 0, s = this.matchMappings[t] = { defaultToken: "text" }, o = "g", u = []; for (var a = 0; a < n.length; a++) { var f = n[a]; f.defaultToken && (s.defaultToken = f.defaultToken), f.caseInsensitive && (o = "gi"); if (f.regex == null) continue; f.regex instanceof RegExp && (f.regex = f.regex.toString().slice(1, -1)); var l = f.regex, c = (new RegExp("(?:(" + l + ")|(.))")).exec("a").length - 2; Array.isArray(f.token) ? f.token.length == 1 || c == 1 ? f.token = f.token[0] : c - 1 != f.token.length ? (this.reportError("number of classes and regexp groups doesn't match", { rule: f, groupCount: c - 1 }), f.token = f.token[0]) : (f.tokenArray = f.token, f.token = null, f.onMatch = this.$arrayTokens) : typeof f.token == "function" && !f.onMatch && (c > 1 ? f.onMatch = this.$applyToken : f.onMatch = f.token), c > 1 && (/\\\d/.test(f.regex) ? l = f.regex.replace(/\\([0-9]+)/g, function (e, t) { return "\\" + (parseInt(t, 10) + i + 1) }) : (c = 1, l = this.removeCapturingGroups(f.regex)), !f.splitRegex && typeof f.token != "string" && u.push(f)), s[i] = a, i += c, r.push(l), f.onMatch || (f.onMatch = null) } r.length || (s[0] = 0, r.push("$")), u.forEach(function (e) { e.splitRegex = this.createSplitterRegexp(e.regex, o) }, this), this.regExps[t] = new RegExp("(" + r.join(")|(") + ")|($)", o) } }; (function () { this.$setMaxTokenCount = function (e) { i = e | 0 }, this.$applyToken = function (e) { var t = this.splitRegex.exec(e).slice(1), n = this.token.apply(this, t); if (typeof n == "string") return [{ type: n, value: e }]; var r = []; for (var i = 0, s = n.length; i < s; i++)t[i] && (r[r.length] = { type: n[i], value: t[i] }); return r }, this.$arrayTokens = function (e) { if (!e) return []; var t = this.splitRegex.exec(e); if (!t) return "text"; var n = [], r = this.tokenArray; for (var i = 0, s = r.length; i < s; i++)t[i + 1] && (n[n.length] = { type: r[i], value: t[i + 1] }); return n }, this.removeCapturingGroups = function (e) { var t = e.replace(/\\.|\[(?:\\.|[^\\\]])*|\(\?[:=!]|(\()/g, function (e, t) { return t ? "(?:" : e }); return t }, this.createSplitterRegexp = function (e, t) { if (e.indexOf("(?=") != -1) { var n = 0, r = !1, i = {}; e.replace(/(\\.)|(\((?:\?[=!])?)|(\))|([\[\]])/g, function (e, t, s, o, u, a) { return r ? r = u != "]" : u ? r = !0 : o ? (n == i.stack && (i.end = a + 1, i.stack = -1), n--) : s && (n++, s.length != 1 && (i.stack = n, i.start = a)), e }), i.end != null && /^\)*$/.test(e.substr(i.end)) && (e = e.substring(0, i.start) + e.substr(i.end)) } return e.charAt(0) != "^" && (e = "^" + e), e.charAt(e.length - 1) != "$" && (e += "$"), new RegExp(e, (t || "").replace("g", "")) }, this.getLineTokens = function (e, t) { if (t && typeof t != "string") { var n = t.slice(0); t = n[0], t === "#tmp" && (n.shift(), t = n.shift()) } else var n = []; var r = t || "start", s = this.states[r]; s || (r = "start", s = this.states[r]); var o = this.matchMappings[r], u = this.regExps[r]; u.lastIndex = 0; var a, f = [], l = 0, c = 0, h = { type: null, value: "" }; while (a = u.exec(e)) { var p = o.defaultToken, d = null, v = a[0], m = u.lastIndex; if (m - v.length > l) { var g = e.substring(l, m - v.length); h.type == p ? h.value += g : (h.type && f.push(h), h = { type: p, value: g }) } for (var y = 0; y < a.length - 2; y++) { if (a[y + 1] === undefined) continue; d = s[o[y]], d.onMatch ? p = d.onMatch(v, r, n, e) : p = d.token, d.next && (typeof d.next == "string" ? r = d.next : r = d.next(r, n), s = this.states[r], s || (this.reportError("state doesn't exist", r), r = "start", s = this.states[r]), o = this.matchMappings[r], l = m, u = this.regExps[r], u.lastIndex = m), d.consumeLineEnd && (l = m); break } if (v) if (typeof p == "string") !!d && d.merge === !1 || h.type !== p ? (h.type && f.push(h), h = { type: p, value: v }) : h.value += v; else if (p) { h.type && f.push(h), h = { type: null, value: "" }; for (var y = 0; y < p.length; y++)f.push(p[y]) } if (l == e.length) break; l = m; if (c++ > i) { c > 2 * e.length && this.reportError("infinite loop with in ace tokenizer", { startState: t, line: e }); while (l < e.length) h.type && f.push(h), h = { value: e.substring(l, l += 500), type: "overflow" }; r = "start", n = []; break } } return h.type && f.push(h), n.length > 1 && n[0] !== r && n.unshift("#tmp", r), { tokens: f, state: n.length ? n : r } }, this.reportError = r.reportError }).call(s.prototype), t.Tokenizer = s }), define("ace/mode/text_highlight_rules", ["require", "exports", "module", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("../lib/lang"), i = function () { this.$rules = { start: [{ token: "empty_line", regex: "^$" }, { defaultToken: "text" }] } }; (function () { this.addRules = function (e, t) { if (!t) { for (var n in e) this.$rules[n] = e[n]; return } for (var n in e) { var r = e[n]; for (var i = 0; i < r.length; i++) { var s = r[i]; if (s.next || s.onMatch) typeof s.next == "string" && s.next.indexOf(t) !== 0 && (s.next = t + s.next), s.nextState && s.nextState.indexOf(t) !== 0 && (s.nextState = t + s.nextState) } this.$rules[t + n] = r } }, this.getRules = function () { return this.$rules }, this.embedRules = function (e, t, n, i, s) { var o = typeof e == "function" ? (new e).getRules() : e; if (i) for (var u = 0; u < i.length; u++)i[u] = t + i[u]; else { i = []; for (var a in o) i.push(t + a) } this.addRules(o, t); if (n) { var f = Array.prototype[s ? "push" : "unshift"]; for (var u = 0; u < i.length; u++)f.apply(this.$rules[i[u]], r.deepCopy(n)) } this.$embeds || (this.$embeds = []), this.$embeds.push(t) }, this.getEmbeds = function () { return this.$embeds }; var e = function (e, t) { return (e != "start" || t.length) && t.unshift(this.nextState, e), this.nextState }, t = function (e, t) { return t.shift(), t.shift() || "start" }; this.normalizeRules = function () { function i(s) { var o = r[s]; o.processed = !0; for (var u = 0; u < o.length; u++) { var a = o[u], f = null; Array.isArray(a) && (f = a, a = {}), !a.regex && a.start && (a.regex = a.start, a.next || (a.next = []), a.next.push({ defaultToken: a.token }, { token: a.token + ".end", regex: a.end || a.start, next: "pop" }), a.token = a.token + ".start", a.push = !0); var l = a.next || a.push; if (l && Array.isArray(l)) { var c = a.stateName; c || (c = a.token, typeof c != "string" && (c = c[0] || ""), r[c] && (c += n++)), r[c] = l, a.next = c, i(c) } else l == "pop" && (a.next = t); a.push && (a.nextState = a.next || a.push, a.next = e, delete a.push); if (a.rules) for (var h in a.rules) r[h] ? r[h].push && r[h].push.apply(r[h], a.rules[h]) : r[h] = a.rules[h]; var p = typeof a == "string" ? a : a.include; p && (Array.isArray(p) ? f = p.map(function (e) { return r[e] }) : f = r[p]); if (f) { var d = [u, 1].concat(f); a.noEscape && (d = d.filter(function (e) { return !e.next })), o.splice.apply(o, d), u-- } a.keywordMap && (a.token = this.createKeywordMapper(a.keywordMap, a.defaultToken || "text", a.caseInsensitive), delete a.defaultToken) } } var n = 0, r = this.$rules; Object.keys(r).forEach(i, this) }, this.createKeywordMapper = function (e, t, n, r) { var i = Object.create(null); return Object.keys(e).forEach(function (t) { var s = e[t]; n && (s = s.toLowerCase()); var o = s.split(r || "|"); for (var u = o.length; u--;)i[o[u]] = t }), Object.getPrototypeOf(i) && (i.__proto__ = null), this.$keywordList = Object.keys(i), e = null, n ? function (e) { return i[e.toLowerCase()] || t } : function (e) { return i[e] || t } }, this.getKeywords = function () { return this.$keywords } }).call(i.prototype), t.TextHighlightRules = i }), define("ace/mode/behaviour", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = function () { this.$behaviours = {} }; (function () { this.add = function (e, t, n) { switch (undefined) { case this.$behaviours: this.$behaviours = {}; case this.$behaviours[e]: this.$behaviours[e] = {} }this.$behaviours[e][t] = n }, this.addBehaviours = function (e) { for (var t in e) for (var n in e[t]) this.add(t, n, e[t][n]) }, this.remove = function (e) { this.$behaviours && this.$behaviours[e] && delete this.$behaviours[e] }, this.inherit = function (e, t) { if (typeof e == "function") var n = (new e).getBehaviours(t); else var n = e.getBehaviours(t); this.addBehaviours(n) }, this.getBehaviours = function (e) { if (!e) return this.$behaviours; var t = {}; for (var n = 0; n < e.length; n++)this.$behaviours[e[n]] && (t[e[n]] = this.$behaviours[e[n]]); return t } }).call(r.prototype), t.Behaviour = r }), define("ace/token_iterator", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = function (e, t, n) { this.$session = e, this.$row = t, this.$rowTokens = e.getTokens(t); var r = e.getTokenAt(t, n); this.$tokenIndex = r ? r.index : -1 }; (function () { this.stepBackward = function () { this.$tokenIndex -= 1; while (this.$tokenIndex < 0) { this.$row -= 1; if (this.$row < 0) return this.$row = 0, null; this.$rowTokens = this.$session.getTokens(this.$row), this.$tokenIndex = this.$rowTokens.length - 1 } return this.$rowTokens[this.$tokenIndex] }, this.stepForward = function () { this.$tokenIndex += 1; var e; while (this.$tokenIndex >= this.$rowTokens.length) { this.$row += 1, e || (e = this.$session.getLength()); if (this.$row >= e) return this.$row = e - 1, null; this.$rowTokens = this.$session.getTokens(this.$row), this.$tokenIndex = 0 } return this.$rowTokens[this.$tokenIndex] }, this.getCurrentToken = function () { return this.$rowTokens[this.$tokenIndex] }, this.getCurrentTokenRow = function () { return this.$row }, this.getCurrentTokenColumn = function () { var e = this.$rowTokens, t = this.$tokenIndex, n = e[t].start; if (n !== undefined) return n; n = 0; while (t > 0) t -= 1, n += e[t].value.length; return n }, this.getCurrentTokenPosition = function () { return { row: this.$row, column: this.getCurrentTokenColumn() } }, this.getCurrentTokenRange = function () { var e = this.$rowTokens[this.$tokenIndex], t = this.getCurrentTokenColumn(); return new r(this.$row, t, this.$row, t + e.value.length) } }).call(i.prototype), t.TokenIterator = i }), define("ace/mode/behaviour/cstyle", ["require", "exports", "module", "ace/lib/oop", "ace/mode/behaviour", "ace/token_iterator", "ace/lib/lang"], function (e, t, n) { "use strict"; var r = e("../../lib/oop"), i = e("../behaviour").Behaviour, s = e("../../token_iterator").TokenIterator, o = e("../../lib/lang"), u = ["text", "paren.rparen", "rparen", "paren", "punctuation.operator"], a = ["text", "paren.rparen", "rparen", "paren", "punctuation.operator", "comment"], f, l = {}, c = { '"': '"', "'": "'" }, h = function (e) { var t = -1; e.multiSelect && (t = e.selection.index, l.rangeCount != e.multiSelect.rangeCount && (l = { rangeCount: e.multiSelect.rangeCount })); if (l[t]) return f = l[t]; f = l[t] = { autoInsertedBrackets: 0, autoInsertedRow: -1, autoInsertedLineEnd: "", maybeInsertedBrackets: 0, maybeInsertedRow: -1, maybeInsertedLineStart: "", maybeInsertedLineEnd: "" } }, p = function (e, t, n, r) { var i = e.end.row - e.start.row; return { text: n + t + r, selection: [0, e.start.column + 1, i, e.end.column + (i ? 0 : 1)] } }, d = function (e) { this.add("braces", "insertion", function (t, n, r, i, s) { var u = r.getCursorPosition(), a = i.doc.getLine(u.row); if (s == "{") { h(r); var l = r.getSelectionRange(), c = i.doc.getTextRange(l); if (c !== "" && c !== "{" && r.getWrapBehavioursEnabled()) return p(l, c, "{", "}"); if (d.isSaneInsertion(r, i)) return /[\]\}\)]/.test(a[u.column]) || r.inMultiSelectMode || e && e.braces ? (d.recordAutoInsert(r, i, "}"), { text: "{}", selection: [1, 1] }) : (d.recordMaybeInsert(r, i, "{"), { text: "{", selection: [1, 1] }) } else if (s == "}") { h(r); var v = a.substring(u.column, u.column + 1); if (v == "}") { var m = i.$findOpeningBracket("}", { column: u.column + 1, row: u.row }); if (m !== null && d.isAutoInsertedClosing(u, a, s)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } else { if (s == "\n" || s == "\r\n") { h(r); var g = ""; d.isMaybeInsertedClosing(u, a) && (g = o.stringRepeat("}", f.maybeInsertedBrackets), d.clearMaybeInsertedClosing()); var v = a.substring(u.column, u.column + 1); if (v === "}") { var y = i.findMatchingBracket({ row: u.row, column: u.column + 1 }, "}"); if (!y) return null; var b = this.$getIndent(i.getLine(y.row)) } else { if (!g) { d.clearMaybeInsertedClosing(); return } var b = this.$getIndent(a) } var w = b + i.getTabString(); return { text: "\n" + w + "\n" + b + g, selection: [1, w.length, 1, w.length] } } d.clearMaybeInsertedClosing() } }), this.add("braces", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "{") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.end.column, i.end.column + 1); if (u == "}") return i.end.column++, i; f.maybeInsertedBrackets-- } }), this.add("parens", "insertion", function (e, t, n, r, i) { if (i == "(") { h(n); var s = n.getSelectionRange(), o = r.doc.getTextRange(s); if (o !== "" && n.getWrapBehavioursEnabled()) return p(s, o, "(", ")"); if (d.isSaneInsertion(n, r)) return d.recordAutoInsert(n, r, ")"), { text: "()", selection: [1, 1] } } else if (i == ")") { h(n); var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); if (f == ")") { var l = r.$findOpeningBracket(")", { column: u.column + 1, row: u.row }); if (l !== null && d.isAutoInsertedClosing(u, a, i)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } }), this.add("parens", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "(") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); if (u == ")") return i.end.column++, i } }), this.add("brackets", "insertion", function (e, t, n, r, i) { if (i == "[") { h(n); var s = n.getSelectionRange(), o = r.doc.getTextRange(s); if (o !== "" && n.getWrapBehavioursEnabled()) return p(s, o, "[", "]"); if (d.isSaneInsertion(n, r)) return d.recordAutoInsert(n, r, "]"), { text: "[]", selection: [1, 1] } } else if (i == "]") { h(n); var u = n.getCursorPosition(), a = r.doc.getLine(u.row), f = a.substring(u.column, u.column + 1); if (f == "]") { var l = r.$findOpeningBracket("]", { column: u.column + 1, row: u.row }); if (l !== null && d.isAutoInsertedClosing(u, a, i)) return d.popAutoInsertedClosing(), { text: "", selection: [1, 1] } } } }), this.add("brackets", "deletion", function (e, t, n, r, i) { var s = r.doc.getTextRange(i); if (!i.isMultiLine() && s == "[") { h(n); var o = r.doc.getLine(i.start.row), u = o.substring(i.start.column + 1, i.start.column + 2); if (u == "]") return i.end.column++, i } }), this.add("string_dquotes", "insertion", function (e, t, n, r, i) { var s = r.$mode.$quotes || c; if (i.length == 1 && s[i]) { if (this.lineCommentStart && this.lineCommentStart.indexOf(i) != -1) return; h(n); var o = i, u = n.getSelectionRange(), a = r.doc.getTextRange(u); if (a !== "" && (a.length != 1 || !s[a]) && n.getWrapBehavioursEnabled()) return p(u, a, o, o); if (!a) { var f = n.getCursorPosition(), l = r.doc.getLine(f.row), d = l.substring(f.column - 1, f.column), v = l.substring(f.column, f.column + 1), m = r.getTokenAt(f.row, f.column), g = r.getTokenAt(f.row, f.column + 1); if (d == "\\" && m && /escape/.test(m.type)) return null; var y = m && /string|escape/.test(m.type), b = !g || /string|escape/.test(g.type), w; if (v == o) w = y !== b, w && /string\.end/.test(g.type) && (w = !1); else { if (y && !b) return null; if (y && b) return null; var E = r.$mode.tokenRe; E.lastIndex = 0; var S = E.test(d); E.lastIndex = 0; var x = E.test(d); if (S || x) return null; if (v && !/[\s;,.})\]\\]/.test(v)) return null; var T = l[f.column - 2]; if (!(d != o || T != o && !E.test(T))) return null; w = !0 } return { text: w ? o + o : "", selection: [1, 1] } } } }), this.add("string_dquotes", "deletion", function (e, t, n, r, i) { var s = r.$mode.$quotes || c, o = r.doc.getTextRange(i); if (!i.isMultiLine() && s.hasOwnProperty(o)) { h(n); var u = r.doc.getLine(i.start.row), a = u.substring(i.start.column + 1, i.start.column + 2); if (a == o) return i.end.column++, i } }) }; d.isSaneInsertion = function (e, t) { var n = e.getCursorPosition(), r = new s(t, n.row, n.column); if (!this.$matchTokenType(r.getCurrentToken() || "text", u)) { if (/[)}\]]/.test(e.session.getLine(n.row)[n.column])) return !0; var i = new s(t, n.row, n.column + 1); if (!this.$matchTokenType(i.getCurrentToken() || "text", u)) return !1 } return r.stepForward(), r.getCurrentTokenRow() !== n.row || this.$matchTokenType(r.getCurrentToken() || "text", a) }, d.$matchTokenType = function (e, t) { return t.indexOf(e.type || e) > -1 }, d.recordAutoInsert = function (e, t, n) { var r = e.getCursorPosition(), i = t.doc.getLine(r.row); this.isAutoInsertedClosing(r, i, f.autoInsertedLineEnd[0]) || (f.autoInsertedBrackets = 0), f.autoInsertedRow = r.row, f.autoInsertedLineEnd = n + i.substr(r.column), f.autoInsertedBrackets++ }, d.recordMaybeInsert = function (e, t, n) { var r = e.getCursorPosition(), i = t.doc.getLine(r.row); this.isMaybeInsertedClosing(r, i) || (f.maybeInsertedBrackets = 0), f.maybeInsertedRow = r.row, f.maybeInsertedLineStart = i.substr(0, r.column) + n, f.maybeInsertedLineEnd = i.substr(r.column), f.maybeInsertedBrackets++ }, d.isAutoInsertedClosing = function (e, t, n) { return f.autoInsertedBrackets > 0 && e.row === f.autoInsertedRow && n === f.autoInsertedLineEnd[0] && t.substr(e.column) === f.autoInsertedLineEnd }, d.isMaybeInsertedClosing = function (e, t) { return f.maybeInsertedBrackets > 0 && e.row === f.maybeInsertedRow && t.substr(e.column) === f.maybeInsertedLineEnd && t.substr(0, e.column) == f.maybeInsertedLineStart }, d.popAutoInsertedClosing = function () { f.autoInsertedLineEnd = f.autoInsertedLineEnd.substr(1), f.autoInsertedBrackets-- }, d.clearMaybeInsertedClosing = function () { f && (f.maybeInsertedBrackets = 0, f.maybeInsertedRow = -1) }, r.inherits(d, i), t.CstyleBehaviour = d }), define("ace/unicode", ["require", "exports", "module"], function (e, t, n) { "use strict"; var r = [48, 9, 8, 25, 5, 0, 2, 25, 48, 0, 11, 0, 5, 0, 6, 22, 2, 30, 2, 457, 5, 11, 15, 4, 8, 0, 2, 0, 18, 116, 2, 1, 3, 3, 9, 0, 2, 2, 2, 0, 2, 19, 2, 82, 2, 138, 2, 4, 3, 155, 12, 37, 3, 0, 8, 38, 10, 44, 2, 0, 2, 1, 2, 1, 2, 0, 9, 26, 6, 2, 30, 10, 7, 61, 2, 9, 5, 101, 2, 7, 3, 9, 2, 18, 3, 0, 17, 58, 3, 100, 15, 53, 5, 0, 6, 45, 211, 57, 3, 18, 2, 5, 3, 11, 3, 9, 2, 1, 7, 6, 2, 2, 2, 7, 3, 1, 3, 21, 2, 6, 2, 0, 4, 3, 3, 8, 3, 1, 3, 3, 9, 0, 5, 1, 2, 4, 3, 11, 16, 2, 2, 5, 5, 1, 3, 21, 2, 6, 2, 1, 2, 1, 2, 1, 3, 0, 2, 4, 5, 1, 3, 2, 4, 0, 8, 3, 2, 0, 8, 15, 12, 2, 2, 8, 2, 2, 2, 21, 2, 6, 2, 1, 2, 4, 3, 9, 2, 2, 2, 2, 3, 0, 16, 3, 3, 9, 18, 2, 2, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 3, 8, 3, 1, 3, 2, 9, 1, 5, 1, 2, 4, 3, 9, 2, 0, 17, 1, 2, 5, 4, 2, 2, 3, 4, 1, 2, 0, 2, 1, 4, 1, 4, 2, 4, 11, 5, 4, 4, 2, 2, 3, 3, 0, 7, 0, 15, 9, 18, 2, 2, 7, 2, 2, 2, 22, 2, 9, 2, 4, 4, 7, 2, 2, 2, 3, 8, 1, 2, 1, 7, 3, 3, 9, 19, 1, 2, 7, 2, 2, 2, 22, 2, 9, 2, 4, 3, 8, 2, 2, 2, 3, 8, 1, 8, 0, 2, 3, 3, 9, 19, 1, 2, 7, 2, 2, 2, 22, 2, 15, 4, 7, 2, 2, 2, 3, 10, 0, 9, 3, 3, 9, 11, 5, 3, 1, 2, 17, 4, 23, 2, 8, 2, 0, 3, 6, 4, 0, 5, 5, 2, 0, 2, 7, 19, 1, 14, 57, 6, 14, 2, 9, 40, 1, 2, 0, 3, 1, 2, 0, 3, 0, 7, 3, 2, 6, 2, 2, 2, 0, 2, 0, 3, 1, 2, 12, 2, 2, 3, 4, 2, 0, 2, 5, 3, 9, 3, 1, 35, 0, 24, 1, 7, 9, 12, 0, 2, 0, 2, 0, 5, 9, 2, 35, 5, 19, 2, 5, 5, 7, 2, 35, 10, 0, 58, 73, 7, 77, 3, 37, 11, 42, 2, 0, 4, 328, 2, 3, 3, 6, 2, 0, 2, 3, 3, 40, 2, 3, 3, 32, 2, 3, 3, 6, 2, 0, 2, 3, 3, 14, 2, 56, 2, 3, 3, 66, 5, 0, 33, 15, 17, 84, 13, 619, 3, 16, 2, 25, 6, 74, 22, 12, 2, 6, 12, 20, 12, 19, 13, 12, 2, 2, 2, 1, 13, 51, 3, 29, 4, 0, 5, 1, 3, 9, 34, 2, 3, 9, 7, 87, 9, 42, 6, 69, 11, 28, 4, 11, 5, 11, 11, 39, 3, 4, 12, 43, 5, 25, 7, 10, 38, 27, 5, 62, 2, 28, 3, 10, 7, 9, 14, 0, 89, 75, 5, 9, 18, 8, 13, 42, 4, 11, 71, 55, 9, 9, 4, 48, 83, 2, 2, 30, 14, 230, 23, 280, 3, 5, 3, 37, 3, 5, 3, 7, 2, 0, 2, 0, 2, 0, 2, 30, 3, 52, 2, 6, 2, 0, 4, 2, 2, 6, 4, 3, 3, 5, 5, 12, 6, 2, 2, 6, 67, 1, 20, 0, 29, 0, 14, 0, 17, 4, 60, 12, 5, 0, 4, 11, 18, 0, 5, 0, 3, 9, 2, 0, 4, 4, 7, 0, 2, 0, 2, 0, 2, 3, 2, 10, 3, 3, 6, 4, 5, 0, 53, 1, 2684, 46, 2, 46, 2, 132, 7, 6, 15, 37, 11, 53, 10, 0, 17, 22, 10, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 6, 2, 31, 48, 0, 470, 1, 36, 5, 2, 4, 6, 1, 5, 85, 3, 1, 3, 2, 2, 89, 2, 3, 6, 40, 4, 93, 18, 23, 57, 15, 513, 6581, 75, 20939, 53, 1164, 68, 45, 3, 268, 4, 27, 21, 31, 3, 13, 13, 1, 2, 24, 9, 69, 11, 1, 38, 8, 3, 102, 3, 1, 111, 44, 25, 51, 13, 68, 12, 9, 7, 23, 4, 0, 5, 45, 3, 35, 13, 28, 4, 64, 15, 10, 39, 54, 10, 13, 3, 9, 7, 22, 4, 1, 5, 66, 25, 2, 227, 42, 2, 1, 3, 9, 7, 11171, 13, 22, 5, 48, 8453, 301, 3, 61, 3, 105, 39, 6, 13, 4, 6, 11, 2, 12, 2, 4, 2, 0, 2, 1, 2, 1, 2, 107, 34, 362, 19, 63, 3, 53, 41, 11, 5, 15, 17, 6, 13, 1, 25, 2, 33, 4, 2, 134, 20, 9, 8, 25, 5, 0, 2, 25, 12, 88, 4, 5, 3, 5, 3, 5, 3, 2], i = 0, s = []; for (var o = 0; o < r.length; o += 2)s.push(i += r[o]), r[o + 1] && s.push(45, i += r[o + 1]); t.wordChars = String.fromCharCode.apply(null, s) }), define("ace/mode/text", ["require", "exports", "module", "ace/config", "ace/tokenizer", "ace/mode/text_highlight_rules", "ace/mode/behaviour/cstyle", "ace/unicode", "ace/lib/lang", "ace/token_iterator", "ace/range"], function (e, t, n) { "use strict"; var r = e("../config"), i = e("../tokenizer").Tokenizer, s = e("./text_highlight_rules").TextHighlightRules, o = e("./behaviour/cstyle").CstyleBehaviour, u = e("../unicode"), a = e("../lib/lang"), f = e("../token_iterator").TokenIterator, l = e("../range").Range, c = function () { this.HighlightRules = s }; (function () { this.$defaultBehaviour = new o, this.tokenRe = new RegExp("^[" + u.wordChars + "\\$_]+", "g"), this.nonTokenRe = new RegExp("^(?:[^" + u.wordChars + "\\$_]|\\s])+", "g"), this.getTokenizer = function () { return this.$tokenizer || (this.$highlightRules = this.$highlightRules || new this.HighlightRules(this.$highlightRuleConfig), this.$tokenizer = new i(this.$highlightRules.getRules())), this.$tokenizer }, this.lineCommentStart = "", this.blockComment = "", this.toggleCommentLines = function (e, t, n, r) { function w(e) { for (var t = n; t <= r; t++)e(i.getLine(t), t) } var i = t.doc, s = !0, o = !0, u = Infinity, f = t.getTabSize(), l = !1; if (!this.lineCommentStart) { if (!this.blockComment) return !1; var c = this.blockComment.start, h = this.blockComment.end, p = new RegExp("^(\\s*)(?:" + a.escapeRegExp(c) + ")"), d = new RegExp("(?:" + a.escapeRegExp(h) + ")\\s*$"), v = function (e, t) { if (g(e, t)) return; if (!s || /\S/.test(e)) i.insertInLine({ row: t, column: e.length }, h), i.insertInLine({ row: t, column: u }, c) }, m = function (e, t) { var n; (n = e.match(d)) && i.removeInLine(t, e.length - n[0].length, e.length), (n = e.match(p)) && i.removeInLine(t, n[1].length, n[0].length) }, g = function (e, n) { if (p.test(e)) return !0; var r = t.getTokens(n); for (var i = 0; i < r.length; i++)if (r[i].type === "comment") return !0 } } else { if (Array.isArray(this.lineCommentStart)) var p = this.lineCommentStart.map(a.escapeRegExp).join("|"), c = this.lineCommentStart[0]; else var p = a.escapeRegExp(this.lineCommentStart), c = this.lineCommentStart; p = new RegExp("^(\\s*)(?:" + p + ") ?"), l = t.getUseSoftTabs(); var m = function (e, t) { var n = e.match(p); if (!n) return; var r = n[1].length, s = n[0].length; !b(e, r, s) && n[0][s - 1] == " " && s--, i.removeInLine(t, r, s) }, y = c + " ", v = function (e, t) { if (!s || /\S/.test(e)) b(e, u, u) ? i.insertInLine({ row: t, column: u }, y) : i.insertInLine({ row: t, column: u }, c) }, g = function (e, t) { return p.test(e) }, b = function (e, t, n) { var r = 0; while (t-- && e.charAt(t) == " ") r++; if (r % f != 0) return !1; var r = 0; while (e.charAt(n++) == " ") r++; return f > 2 ? r % f != f - 1 : r % f == 0 } } var E = Infinity; w(function (e, t) { var n = e.search(/\S/); n !== -1 ? (n < u && (u = n), o && !g(e, t) && (o = !1)) : E > e.length && (E = e.length) }), u == Infinity && (u = E, s = !1, o = !1), l && u % f != 0 && (u = Math.floor(u / f) * f), w(o ? m : v) }, this.toggleBlockComment = function (e, t, n, r) { var i = this.blockComment; if (!i) return; !i.start && i[0] && (i = i[0]); var s = new f(t, r.row, r.column), o = s.getCurrentToken(), u = t.selection, a = t.selection.toOrientedRange(), c, h; if (o && /comment/.test(o.type)) { var p, d; while (o && /comment/.test(o.type)) { var v = o.value.indexOf(i.start); if (v != -1) { var m = s.getCurrentTokenRow(), g = s.getCurrentTokenColumn() + v; p = new l(m, g, m, g + i.start.length); break } o = s.stepBackward() } var s = new f(t, r.row, r.column), o = s.getCurrentToken(); while (o && /comment/.test(o.type)) { var v = o.value.indexOf(i.end); if (v != -1) { var m = s.getCurrentTokenRow(), g = s.getCurrentTokenColumn() + v; d = new l(m, g, m, g + i.end.length); break } o = s.stepForward() } d && t.remove(d), p && (t.remove(p), c = p.start.row, h = -i.start.length) } else h = i.start.length, c = n.start.row, t.insert(n.end, i.end), t.insert(n.start, i.start); a.start.row == c && (a.start.column += h), a.end.row == c && (a.end.column += h), t.selection.fromOrientedRange(a) }, this.getNextLineIndent = function (e, t, n) { return this.$getIndent(t) }, this.checkOutdent = function (e, t, n) { return !1 }, this.autoOutdent = function (e, t, n) { }, this.$getIndent = function (e) { return e.match(/^\s*/)[0] }, this.createWorker = function (e) { return null }, this.createModeDelegates = function (e) { this.$embeds = [], this.$modes = {}; for (var t in e) if (e[t]) { var n = e[t], i = n.prototype.$id, s = r.$modes[i]; s || (r.$modes[i] = s = new n), r.$modes[t] || (r.$modes[t] = s), this.$embeds.push(t), this.$modes[t] = s } var o = ["toggleBlockComment", "toggleCommentLines", "getNextLineIndent", "checkOutdent", "autoOutdent", "transformAction", "getCompletions"]; for (var t = 0; t < o.length; t++)(function (e) { var n = o[t], r = e[n]; e[o[t]] = function () { return this.$delegator(n, arguments, r) } })(this) }, this.$delegator = function (e, t, n) { var r = t[0] || "start"; if (typeof r != "string") { if (Array.isArray(r[2])) { var i = r[2][r[2].length - 1], s = this.$modes[i]; if (s) return s[e].apply(s, [r[1]].concat([].slice.call(t, 1))) } r = r[0] || "start" } for (var o = 0; o < this.$embeds.length; o++) { if (!this.$modes[this.$embeds[o]]) continue; var u = r.split(this.$embeds[o]); if (!u[0] && u[1]) { t[0] = u[1]; var s = this.$modes[this.$embeds[o]]; return s[e].apply(s, t) } } var a = n.apply(this, t); return n ? a : undefined }, this.transformAction = function (e, t, n, r, i) { if (this.$behaviour) { var s = this.$behaviour.getBehaviours(); for (var o in s) if (s[o][t]) { var u = s[o][t].apply(this, arguments); if (u) return u } } }, this.getKeywords = function (e) { if (!this.completionKeywords) { var t = this.$tokenizer.rules, n = []; for (var r in t) { var i = t[r]; for (var s = 0, o = i.length; s < o; s++)if (typeof i[s].token == "string") /keyword|support|storage/.test(i[s].token) && n.push(i[s].regex); else if (typeof i[s].token == "object") for (var u = 0, a = i[s].token.length; u < a; u++)if (/keyword|support|storage/.test(i[s].token[u])) { var r = i[s].regex.match(/\(.+?\)/g)[u]; n.push(r.substr(1, r.length - 2)) } } this.completionKeywords = n } return e ? n.concat(this.$keywordList || []) : this.$keywordList }, this.$createKeywordList = function () { return this.$highlightRules || this.getTokenizer(), this.$keywordList = this.$highlightRules.$keywordList || [] }, this.getCompletions = function (e, t, n, r) { var i = this.$keywordList || this.$createKeywordList(); return i.map(function (e) { return { name: e, value: e, score: 0, meta: "keyword" } }) }, this.$id = "ace/mode/text" }).call(c.prototype), t.Mode = c }), define("ace/apply_delta", ["require", "exports", "module"], function (e, t, n) { "use strict"; function r(e, t) { throw console.log("Invalid Delta:", e), "Invalid Delta: " + t } function i(e, t) { return t.row >= 0 && t.row < e.length && t.column >= 0 && t.column <= e[t.row].length } function s(e, t) { t.action != "insert" && t.action != "remove" && r(t, "delta.action must be 'insert' or 'remove'"), t.lines instanceof Array || r(t, "delta.lines must be an Array"), (!t.start || !t.end) && r(t, "delta.start/end must be an present"); var n = t.start; i(e, t.start) || r(t, "delta.start must be contained in document"); var s = t.end; t.action == "remove" && !i(e, s) && r(t, "delta.end must contained in document for 'remove' actions"); var o = s.row - n.row, u = s.column - (o == 0 ? n.column : 0); (o != t.lines.length - 1 || t.lines[o].length != u) && r(t, "delta.range must match delta lines") } t.applyDelta = function (e, t, n) { var r = t.start.row, i = t.start.column, s = e[r] || ""; switch (t.action) { case "insert": var o = t.lines; if (o.length === 1) e[r] = s.substring(0, i) + t.lines[0] + s.substring(i); else { var u = [r, 1].concat(t.lines); e.splice.apply(e, u), e[r] = s.substring(0, i) + e[r], e[r + t.lines.length - 1] += s.substring(i) } break; case "remove": var a = t.end.column, f = t.end.row; r === f ? e[r] = s.substring(0, i) + s.substring(a) : e.splice(r, f - r + 1, s.substring(0, i) + e[f].substring(a)) } } }), define("ace/anchor", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/event_emitter").EventEmitter, s = t.Anchor = function (e, t, n) { this.$onChange = this.onChange.bind(this), this.attach(e), typeof n == "undefined" ? this.setPosition(t.row, t.column) : this.setPosition(t, n) }; (function () { function e(e, t, n) { var r = n ? e.column <= t.column : e.column < t.column; return e.row < t.row || e.row == t.row && r } function t(t, n, r) { var i = t.action == "insert", s = (i ? 1 : -1) * (t.end.row - t.start.row), o = (i ? 1 : -1) * (t.end.column - t.start.column), u = t.start, a = i ? u : t.end; return e(n, u, r) ? { row: n.row, column: n.column } : e(a, n, !r) ? { row: n.row + s, column: n.column + (n.row == a.row ? o : 0) } : { row: u.row, column: u.column } } r.implement(this, i), this.getPosition = function () { return this.$clipPositionToDocument(this.row, this.column) }, this.getDocument = function () { return this.document }, this.$insertRight = !1, this.onChange = function (e) { if (e.start.row == e.end.row && e.start.row != this.row) return; if (e.start.row > this.row) return; var n = t(e, { row: this.row, column: this.column }, this.$insertRight); this.setPosition(n.row, n.column, !0) }, this.setPosition = function (e, t, n) { var r; n ? r = { row: e, column: t } : r = this.$clipPositionToDocument(e, t); if (this.row == r.row && this.column == r.column) return; var i = { row: this.row, column: this.column }; this.row = r.row, this.column = r.column, this._signal("change", { old: i, value: r }) }, this.detach = function () { this.document.off("change", this.$onChange) }, this.attach = function (e) { this.document = e || this.document, this.document.on("change", this.$onChange) }, this.$clipPositionToDocument = function (e, t) { var n = {}; return e >= this.document.getLength() ? (n.row = Math.max(0, this.document.getLength() - 1), n.column = this.document.getLine(n.row).length) : e < 0 ? (n.row = 0, n.column = 0) : (n.row = e, n.column = Math.min(this.document.getLine(n.row).length, Math.max(0, t))), t < 0 && (n.column = 0), n } }).call(s.prototype) }), define("ace/document", ["require", "exports", "module", "ace/lib/oop", "ace/apply_delta", "ace/lib/event_emitter", "ace/range", "ace/anchor"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./apply_delta").applyDelta, s = e("./lib/event_emitter").EventEmitter, o = e("./range").Range, u = e("./anchor").Anchor, a = function (e) { this.$lines = [""], e.length === 0 ? this.$lines = [""] : Array.isArray(e) ? this.insertMergedLines({ row: 0, column: 0 }, e) : this.insert({ row: 0, column: 0 }, e) }; (function () { r.implement(this, s), this.setValue = function (e) { var t = this.getLength() - 1; this.remove(new o(0, 0, t, this.getLine(t).length)), this.insert({ row: 0, column: 0 }, e) }, this.getValue = function () { return this.getAllLines().join(this.getNewLineCharacter()) }, this.createAnchor = function (e, t) { return new u(this, e, t) }, "aaa".split(/a/).length === 0 ? this.$split = function (e) { return e.replace(/\r\n|\r/g, "\n").split("\n") } : this.$split = function (e) { return e.split(/\r\n|\r|\n/) }, this.$detectNewLine = function (e) { var t = e.match(/^.*?(\r\n|\r|\n)/m); this.$autoNewLine = t ? t[1] : "\n", this._signal("changeNewLineMode") }, this.getNewLineCharacter = function () { switch (this.$newLineMode) { case "windows": return "\r\n"; case "unix": return "\n"; default: return this.$autoNewLine || "\n" } }, this.$autoNewLine = "", this.$newLineMode = "auto", this.setNewLineMode = function (e) { if (this.$newLineMode === e) return; this.$newLineMode = e, this._signal("changeNewLineMode") }, this.getNewLineMode = function () { return this.$newLineMode }, this.isNewLine = function (e) { return e == "\r\n" || e == "\r" || e == "\n" }, this.getLine = function (e) { return this.$lines[e] || "" }, this.getLines = function (e, t) { return this.$lines.slice(e, t + 1) }, this.getAllLines = function () { return this.getLines(0, this.getLength()) }, this.getLength = function () { return this.$lines.length }, this.getTextRange = function (e) { return this.getLinesForRange(e).join(this.getNewLineCharacter()) }, this.getLinesForRange = function (e) { var t; if (e.start.row === e.end.row) t = [this.getLine(e.start.row).substring(e.start.column, e.end.column)]; else { t = this.getLines(e.start.row, e.end.row), t[0] = (t[0] || "").substring(e.start.column); var n = t.length - 1; e.end.row - e.start.row == n && (t[n] = t[n].substring(0, e.end.column)) } return t }, this.insertLines = function (e, t) { return console.warn("Use of document.insertLines is deprecated. Use the insertFullLines method instead."), this.insertFullLines(e, t) }, this.removeLines = function (e, t) { return console.warn("Use of document.removeLines is deprecated. Use the removeFullLines method instead."), this.removeFullLines(e, t) }, this.insertNewLine = function (e) { return console.warn("Use of document.insertNewLine is deprecated. Use insertMergedLines(position, ['', '']) instead."), this.insertMergedLines(e, ["", ""]) }, this.insert = function (e, t) { return this.getLength() <= 1 && this.$detectNewLine(t), this.insertMergedLines(e, this.$split(t)) }, this.insertInLine = function (e, t) { var n = this.clippedPos(e.row, e.column), r = this.pos(e.row, e.column + t.length); return this.applyDelta({ start: n, end: r, action: "insert", lines: [t] }, !0), this.clonePos(r) }, this.clippedPos = function (e, t) { var n = this.getLength(); e === undefined ? e = n : e < 0 ? e = 0 : e >= n && (e = n - 1, t = undefined); var r = this.getLine(e); return t == undefined && (t = r.length), t = Math.min(Math.max(t, 0), r.length), { row: e, column: t } }, this.clonePos = function (e) { return { row: e.row, column: e.column } }, this.pos = function (e, t) { return { row: e, column: t } }, this.$clipPosition = function (e) { var t = this.getLength(); return e.row >= t ? (e.row = Math.max(0, t - 1), e.column = this.getLine(t - 1).length) : (e.row = Math.max(0, e.row), e.column = Math.min(Math.max(e.column, 0), this.getLine(e.row).length)), e }, this.insertFullLines = function (e, t) { e = Math.min(Math.max(e, 0), this.getLength()); var n = 0; e < this.getLength() ? (t = t.concat([""]), n = 0) : (t = [""].concat(t), e--, n = this.$lines[e].length), this.insertMergedLines({ row: e, column: n }, t) }, this.insertMergedLines = function (e, t) { var n = this.clippedPos(e.row, e.column), r = { row: n.row + t.length - 1, column: (t.length == 1 ? n.column : 0) + t[t.length - 1].length }; return this.applyDelta({ start: n, end: r, action: "insert", lines: t }), this.clonePos(r) }, this.remove = function (e) { var t = this.clippedPos(e.start.row, e.start.column), n = this.clippedPos(e.end.row, e.end.column); return this.applyDelta({ start: t, end: n, action: "remove", lines: this.getLinesForRange({ start: t, end: n }) }), this.clonePos(t) }, this.removeInLine = function (e, t, n) { var r = this.clippedPos(e, t), i = this.clippedPos(e, n); return this.applyDelta({ start: r, end: i, action: "remove", lines: this.getLinesForRange({ start: r, end: i }) }, !0), this.clonePos(r) }, this.removeFullLines = function (e, t) { e = Math.min(Math.max(0, e), this.getLength() - 1), t = Math.min(Math.max(0, t), this.getLength() - 1); var n = t == this.getLength() - 1 && e > 0, r = t < this.getLength() - 1, i = n ? e - 1 : e, s = n ? this.getLine(i).length : 0, u = r ? t + 1 : t, a = r ? 0 : this.getLine(u).length, f = new o(i, s, u, a), l = this.$lines.slice(e, t + 1); return this.applyDelta({ start: f.start, end: f.end, action: "remove", lines: this.getLinesForRange(f) }), l }, this.removeNewLine = function (e) { e < this.getLength() - 1 && e >= 0 && this.applyDelta({ start: this.pos(e, this.getLine(e).length), end: this.pos(e + 1, 0), action: "remove", lines: ["", ""] }) }, this.replace = function (e, t) { e instanceof o || (e = o.fromPoints(e.start, e.end)); if (t.length === 0 && e.isEmpty()) return e.start; if (t == this.getTextRange(e)) return e.end; this.remove(e); var n; return t ? n = this.insert(e.start, t) : n = e.start, n }, this.applyDeltas = function (e) { for (var t = 0; t < e.length; t++)this.applyDelta(e[t]) }, this.revertDeltas = function (e) { for (var t = e.length - 1; t >= 0; t--)this.revertDelta(e[t]) }, this.applyDelta = function (e, t) { var n = e.action == "insert"; if (n ? e.lines.length <= 1 && !e.lines[0] : !o.comparePoints(e.start, e.end)) return; n && e.lines.length > 2e4 ? this.$splitAndapplyLargeDelta(e, 2e4) : (i(this.$lines, e, t), this._signal("change", e)) }, this.$safeApplyDelta = function (e) { var t = this.$lines.length; (e.action == "remove" && e.start.row < t && e.end.row < t || e.action == "insert" && e.start.row <= t) && this.applyDelta(e) }, this.$splitAndapplyLargeDelta = function (e, t) { var n = e.lines, r = n.length - t + 1, i = e.start.row, s = e.start.column; for (var o = 0, u = 0; o < r; o = u) { u += t - 1; var a = n.slice(o, u); a.push(""), this.applyDelta({ start: this.pos(i + o, s), end: this.pos(i + u, s = 0), action: e.action, lines: a }, !0) } e.lines = n.slice(o), e.start.row = i + o, e.start.column = s, this.applyDelta(e, !0) }, this.revertDelta = function (e) { this.$safeApplyDelta({ start: this.clonePos(e.start), end: this.clonePos(e.end), action: e.action == "insert" ? "remove" : "insert", lines: e.lines.slice() }) }, this.indexToPosition = function (e, t) { var n = this.$lines || this.getAllLines(), r = this.getNewLineCharacter().length; for (var i = t || 0, s = n.length; i < s; i++) { e -= n[i].length + r; if (e < 0) return { row: i, column: e + n[i].length + r } } return { row: s - 1, column: e + n[s - 1].length + r } }, this.positionToIndex = function (e, t) { var n = this.$lines || this.getAllLines(), r = this.getNewLineCharacter().length, i = 0, s = Math.min(e.row, n.length); for (var o = t || 0; o < s; ++o)i += n[o].length + r; return i + e.column } }).call(a.prototype), t.Document = a }), define("ace/background_tokenizer", ["require", "exports", "module", "ace/lib/oop", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/event_emitter").EventEmitter, s = function (e, t) { this.running = !1, this.lines = [], this.states = [], this.currentLine = 0, this.tokenizer = e; var n = this; this.$worker = function () { if (!n.running) return; var e = new Date, t = n.currentLine, r = -1, i = n.doc, s = t; while (n.lines[t]) t++; var o = i.getLength(), u = 0; n.running = !1; while (t < o) { n.$tokenizeRow(t), r = t; do t++; while (n.lines[t]); u++; if (u % 5 === 0 && new Date - e > 20) { n.running = setTimeout(n.$worker, 20); break } } n.currentLine = t, r == -1 && (r = t), s <= r && n.fireUpdateEvent(s, r) } }; (function () { r.implement(this, i), this.setTokenizer = function (e) { this.tokenizer = e, this.lines = [], this.states = [], this.start(0) }, this.setDocument = function (e) { this.doc = e, this.lines = [], this.states = [], this.stop() }, this.fireUpdateEvent = function (e, t) { var n = { first: e, last: t }; this._signal("update", { data: n }) }, this.start = function (e) { this.currentLine = Math.min(e || 0, this.currentLine, this.doc.getLength()), this.lines.splice(this.currentLine, this.lines.length), this.states.splice(this.currentLine, this.states.length), this.stop(), this.running = setTimeout(this.$worker, 700) }, this.scheduleStart = function () { this.running || (this.running = setTimeout(this.$worker, 700)) }, this.$updateOnChange = function (e) { var t = e.start.row, n = e.end.row - t; if (n === 0) this.lines[t] = null; else if (e.action == "remove") this.lines.splice(t, n + 1, null), this.states.splice(t, n + 1, null); else { var r = Array(n + 1); r.unshift(t, 1), this.lines.splice.apply(this.lines, r), this.states.splice.apply(this.states, r) } this.currentLine = Math.min(t, this.currentLine, this.doc.getLength()), this.stop() }, this.stop = function () { this.running && clearTimeout(this.running), this.running = !1 }, this.getTokens = function (e) { return this.lines[e] || this.$tokenizeRow(e) }, this.getState = function (e) { return this.currentLine == e && this.$tokenizeRow(e), this.states[e] || "start" }, this.$tokenizeRow = function (e) { var t = this.doc.getLine(e), n = this.states[e - 1], r = this.tokenizer.getLineTokens(t, n, e); return this.states[e] + "" != r.state + "" ? (this.states[e] = r.state, this.lines[e + 1] = null, this.currentLine > e + 1 && (this.currentLine = e + 1)) : this.currentLine == e && (this.currentLine = e + 1), this.lines[e] = r.tokens } }).call(s.prototype), t.BackgroundTokenizer = s }), define("ace/search_highlight", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/range"], function (e, t, n) { "use strict"; var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./range").Range, o = function (e, t, n) { this.setRegexp(e), this.clazz = t, this.type = n || "text" }; (function () { this.MAX_RANGES = 500, this.setRegexp = function (e) { if (this.regExp + "" == e + "") return; this.regExp = e, this.cache = [] }, this.update = function (e, t, n, i) { if (!this.regExp) return; var o = i.firstRow, u = i.lastRow; for (var a = o; a <= u; a++) { var f = this.cache[a]; f == null && (f = r.getMatchOffsets(n.getLine(a), this.regExp), f.length > this.MAX_RANGES && (f = f.slice(0, this.MAX_RANGES)), f = f.map(function (e) { return new s(a, e.offset, a, e.offset + e.length) }), this.cache[a] = f.length ? f : ""); for (var l = f.length; l--;)t.drawSingleLineMarker(e, f[l].toScreenRange(n), this.clazz, i) } } }).call(o.prototype), t.SearchHighlight = o }), define("ace/edit_session/fold_line", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; function i(e, t) { this.foldData = e, Array.isArray(t) ? this.folds = t : t = this.folds = [t]; var n = t[t.length - 1]; this.range = new r(t[0].start.row, t[0].start.column, n.end.row, n.end.column), this.start = this.range.start, this.end = this.range.end, this.folds.forEach(function (e) { e.setFoldLine(this) }, this) } var r = e("../range").Range; (function () { this.shiftRow = function (e) { this.start.row += e, this.end.row += e, this.folds.forEach(function (t) { t.start.row += e, t.end.row += e }) }, this.addFold = function (e) { if (e.sameRow) { if (e.start.row < this.startRow || e.endRow > this.endRow) throw new Error("Can't add a fold to this FoldLine as it has no connection"); this.folds.push(e), this.folds.sort(function (e, t) { return -e.range.compareEnd(t.start.row, t.start.column) }), this.range.compareEnd(e.start.row, e.start.column) > 0 ? (this.end.row = e.end.row, this.end.column = e.end.column) : this.range.compareStart(e.end.row, e.end.column) < 0 && (this.start.row = e.start.row, this.start.column = e.start.column) } else if (e.start.row == this.end.row) this.folds.push(e), this.end.row = e.end.row, this.end.column = e.end.column; else { if (e.end.row != this.start.row) throw new Error("Trying to add fold to FoldRow that doesn't have a matching row"); this.folds.unshift(e), this.start.row = e.start.row, this.start.column = e.start.column } e.foldLine = this }, this.containsRow = function (e) { return e >= this.start.row && e <= this.end.row }, this.walk = function (e, t, n) { var r = 0, i = this.folds, s, o, u, a = !0; t == null && (t = this.end.row, n = this.end.column); for (var f = 0; f < i.length; f++) { s = i[f], o = s.range.compareStart(t, n); if (o == -1) { e(null, t, n, r, a); return } u = e(null, s.start.row, s.start.column, r, a), u = !u && e(s.placeholder, s.start.row, s.start.column, r); if (u || o === 0) return; a = !s.sameRow, r = s.end.column } e(null, t, n, r, a) }, this.getNextFoldTo = function (e, t) { var n, r; for (var i = 0; i < this.folds.length; i++) { n = this.folds[i], r = n.range.compareEnd(e, t); if (r == -1) return { fold: n, kind: "after" }; if (r === 0) return { fold: n, kind: "inside" } } return null }, this.addRemoveChars = function (e, t, n) { var r = this.getNextFoldTo(e, t), i, s; if (r) { i = r.fold; if (r.kind == "inside" && i.start.column != t && i.start.row != e) window.console && window.console.log(e, t, i); else if (i.start.row == e) { s = this.folds; var o = s.indexOf(i); o === 0 && (this.start.column += n); for (o; o < s.length; o++) { i = s[o], i.start.column += n; if (!i.sameRow) return; i.end.column += n } this.end.column += n } } }, this.split = function (e, t) { var n = this.getNextFoldTo(e, t); if (!n || n.kind == "inside") return null; var r = n.fold, s = this.folds, o = this.foldData, u = s.indexOf(r), a = s[u - 1]; this.end.row = a.end.row, this.end.column = a.end.column, s = s.splice(u, s.length - u); var f = new i(o, s); return o.splice(o.indexOf(this) + 1, 0, f), f }, this.merge = function (e) { var t = e.folds; for (var n = 0; n < t.length; n++)this.addFold(t[n]); var r = this.foldData; r.splice(r.indexOf(e), 1) }, this.toString = function () { var e = [this.range.toString() + ": ["]; return this.folds.forEach(function (t) { e.push(" " + t.toString()) }), e.push("]"), e.join("\n") }, this.idxToPosition = function (e) { var t = 0; for (var n = 0; n < this.folds.length; n++) { var r = this.folds[n]; e -= r.start.column - t; if (e < 0) return { row: r.start.row, column: r.start.column + e }; e -= r.placeholder.length; if (e < 0) return r.start; t = r.end.column } return { row: this.end.row, column: this.end.column + e } } }).call(i.prototype), t.FoldLine = i }), define("ace/range_list", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = r.comparePoints, s = function () { this.ranges = [], this.$bias = 1 }; (function () { this.comparePoints = i, this.pointIndex = function (e, t, n) { var r = this.ranges; for (var s = n || 0; s < r.length; s++) { var o = r[s], u = i(e, o.end); if (u > 0) continue; var a = i(e, o.start); return u === 0 ? t && a !== 0 ? -s - 2 : s : a > 0 || a === 0 && !t ? s : -s - 1 } return -s - 1 }, this.add = function (e) { var t = !e.isEmpty(), n = this.pointIndex(e.start, t); n < 0 && (n = -n - 1); var r = this.pointIndex(e.end, t, n); return r < 0 ? r = -r - 1 : r++, this.ranges.splice(n, r - n, e) }, this.addList = function (e) { var t = []; for (var n = e.length; n--;)t.push.apply(t, this.add(e[n])); return t }, this.substractPoint = function (e) { var t = this.pointIndex(e); if (t >= 0) return this.ranges.splice(t, 1) }, this.merge = function () { var e = [], t = this.ranges; t = t.sort(function (e, t) { return i(e.start, t.start) }); var n = t[0], r; for (var s = 1; s < t.length; s++) { r = n, n = t[s]; var o = i(r.end, n.start); if (o < 0) continue; if (o == 0 && !r.isEmpty() && !n.isEmpty()) continue; i(r.end, n.end) < 0 && (r.end.row = n.end.row, r.end.column = n.end.column), t.splice(s, 1), e.push(n), n = r, s-- } return this.ranges = t, e }, this.contains = function (e, t) { return this.pointIndex({ row: e, column: t }) >= 0 }, this.containsPoint = function (e) { return this.pointIndex(e) >= 0 }, this.rangeAtPoint = function (e) { var t = this.pointIndex(e); if (t >= 0) return this.ranges[t] }, this.clipRows = function (e, t) { var n = this.ranges; if (n[0].start.row > t || n[n.length - 1].start.row < e) return []; var r = this.pointIndex({ row: e, column: 0 }); r < 0 && (r = -r - 1); var i = this.pointIndex({ row: t, column: 0 }, r); i < 0 && (i = -i - 1); var s = []; for (var o = r; o < i; o++)s.push(n[o]); return s }, this.removeAll = function () { return this.ranges.splice(0, this.ranges.length) }, this.attach = function (e) { this.session && this.detach(), this.session = e, this.onChange = this.$onChange.bind(this), this.session.on("change", this.onChange) }, this.detach = function () { if (!this.session) return; this.session.removeListener("change", this.onChange), this.session = null }, this.$onChange = function (e) { var t = e.start, n = e.end, r = t.row, i = n.row, s = this.ranges; for (var o = 0, u = s.length; o < u; o++) { var a = s[o]; if (a.end.row >= r) break } if (e.action == "insert") { var f = i - r, l = -t.column + n.column; for (; o < u; o++) { var a = s[o]; if (a.start.row > r) break; a.start.row == r && a.start.column >= t.column && (a.start.column == t.column && this.$bias <= 0 || (a.start.column += l, a.start.row += f)); if (a.end.row == r && a.end.column >= t.column) { if (a.end.column == t.column && this.$bias < 0) continue; a.end.column == t.column && l > 0 && o < u - 1 && a.end.column > a.start.column && a.end.column == s[o + 1].start.column && (a.end.column -= l), a.end.column += l, a.end.row += f } } } else { var f = r - i, l = t.column - n.column; for (; o < u; o++) { var a = s[o]; if (a.start.row > i) break; if (a.end.row < i && (r < a.end.row || r == a.end.row && t.column < a.end.column)) a.end.row = r, a.end.column = t.column; else if (a.end.row == i) if (a.end.column <= n.column) { if (f || a.end.column > t.column) a.end.column = t.column, a.end.row = t.row } else a.end.column += l, a.end.row += f; else a.end.row > i && (a.end.row += f); if (a.start.row < i && (r < a.start.row || r == a.start.row && t.column < a.start.column)) a.start.row = r, a.start.column = t.column; else if (a.start.row == i) if (a.start.column <= n.column) { if (f || a.start.column > t.column) a.start.column = t.column, a.start.row = t.row } else a.start.column += l, a.start.row += f; else a.start.row > i && (a.start.row += f) } } if (f != 0 && o < u) for (; o < u; o++) { var a = s[o]; a.start.row += f, a.end.row += f } } }).call(s.prototype), t.RangeList = s }), define("ace/edit_session/fold", ["require", "exports", "module", "ace/range_list", "ace/lib/oop"], function (e, t, n) { "use strict"; function o(e, t) { e.row -= t.row, e.row == 0 && (e.column -= t.column) } function u(e, t) { o(e.start, t), o(e.end, t) } function a(e, t) { e.row == 0 && (e.column += t.column), e.row += t.row } function f(e, t) { a(e.start, t), a(e.end, t) } var r = e("../range_list").RangeList, i = e("../lib/oop"), s = t.Fold = function (e, t) { this.foldLine = null, this.placeholder = t, this.range = e, this.start = e.start, this.end = e.end, this.sameRow = e.start.row == e.end.row, this.subFolds = this.ranges = [] }; i.inherits(s, r), function () { this.toString = function () { return '"' + this.placeholder + '" ' + this.range.toString() }, this.setFoldLine = function (e) { this.foldLine = e, this.subFolds.forEach(function (t) { t.setFoldLine(e) }) }, this.clone = function () { var e = this.range.clone(), t = new s(e, this.placeholder); return this.subFolds.forEach(function (e) { t.subFolds.push(e.clone()) }), t.collapseChildren = this.collapseChildren, t }, this.addSubFold = function (e) { if (this.range.isEqual(e)) return; u(e, this.start); var t = e.start.row, n = e.start.column; for (var r = 0, i = -1; r < this.subFolds.length; r++) { i = this.subFolds[r].range.compare(t, n); if (i != 1) break } var s = this.subFolds[r], o = 0; if (i == 0) { if (s.range.containsRange(e)) return s.addSubFold(e); o = 1 } var t = e.range.end.row, n = e.range.end.column; for (var a = r, i = -1; a < this.subFolds.length; a++) { i = this.subFolds[a].range.compare(t, n); if (i != 1) break } i == 0 && a++; var f = this.subFolds.splice(r, a - r, e), l = i == 0 ? f.length - 1 : f.length; for (var c = o; c < l; c++)e.addSubFold(f[c]); return e.setFoldLine(this.foldLine), e }, this.restoreRange = function (e) { return f(e, this.start) } }.call(s.prototype) }), define("ace/edit_session/folding", ["require", "exports", "module", "ace/range", "ace/edit_session/fold_line", "ace/edit_session/fold", "ace/token_iterator"], function (e, t, n) { "use strict"; function u() { this.getFoldAt = function (e, t, n) { var r = this.getFoldLine(e); if (!r) return null; var i = r.folds; for (var s = 0; s < i.length; s++) { var o = i[s].range; if (o.contains(e, t)) { if (n == 1 && o.isEnd(e, t) && !o.isEmpty()) continue; if (n == -1 && o.isStart(e, t) && !o.isEmpty()) continue; return i[s] } } }, this.getFoldsInRange = function (e) { var t = e.start, n = e.end, r = this.$foldData, i = []; t.column += 1, n.column -= 1; for (var s = 0; s < r.length; s++) { var o = r[s].range.compareRange(e); if (o == 2) continue; if (o == -2) break; var u = r[s].folds; for (var a = 0; a < u.length; a++) { var f = u[a]; o = f.range.compareRange(e); if (o == -2) break; if (o == 2) continue; if (o == 42) break; i.push(f) } } return t.column -= 1, n.column += 1, i }, this.getFoldsInRangeList = function (e) { if (Array.isArray(e)) { var t = []; e.forEach(function (e) { t = t.concat(this.getFoldsInRange(e)) }, this) } else var t = this.getFoldsInRange(e); return t }, this.getAllFolds = function () { var e = [], t = this.$foldData; for (var n = 0; n < t.length; n++)for (var r = 0; r < t[n].folds.length; r++)e.push(t[n].folds[r]); return e }, this.getFoldStringAt = function (e, t, n, r) { r = r || this.getFoldLine(e); if (!r) return null; var i = { end: { column: 0 } }, s, o; for (var u = 0; u < r.folds.length; u++) { o = r.folds[u]; var a = o.range.compareEnd(e, t); if (a == -1) { s = this.getLine(o.start.row).substring(i.end.column, o.start.column); break } if (a === 0) return null; i = o } return s || (s = this.getLine(o.start.row).substring(i.end.column)), n == -1 ? s.substring(0, t - i.end.column) : n == 1 ? s.substring(t - i.end.column) : s }, this.getFoldLine = function (e, t) { var n = this.$foldData, r = 0; t && (r = n.indexOf(t)), r == -1 && (r = 0); for (r; r < n.length; r++) { var i = n[r]; if (i.start.row <= e && i.end.row >= e) return i; if (i.end.row > e) return null } return null }, this.getNextFoldLine = function (e, t) { var n = this.$foldData, r = 0; t && (r = n.indexOf(t)), r == -1 && (r = 0); for (r; r < n.length; r++) { var i = n[r]; if (i.end.row >= e) return i } return null }, this.getFoldedRowCount = function (e, t) { var n = this.$foldData, r = t - e + 1; for (var i = 0; i < n.length; i++) { var s = n[i], o = s.end.row, u = s.start.row; if (o >= t) { u < t && (u >= e ? r -= t - u : r = 0); break } o >= e && (u >= e ? r -= o - u : r -= o - e + 1) } return r }, this.$addFoldLine = function (e) { return this.$foldData.push(e), this.$foldData.sort(function (e, t) { return e.start.row - t.start.row }), e }, this.addFold = function (e, t) { var n = this.$foldData, r = !1, o; e instanceof s ? o = e : (o = new s(t, e), o.collapseChildren = t.collapseChildren), this.$clipRangeToDocument(o.range); var u = o.start.row, a = o.start.column, f = o.end.row, l = o.end.column, c = this.getFoldAt(u, a, 1), h = this.getFoldAt(f, l, -1); if (c && h == c) return c.addSubFold(o); c && !c.range.isStart(u, a) && this.removeFold(c), h && !h.range.isEnd(f, l) && this.removeFold(h); var p = this.getFoldsInRange(o.range); p.length > 0 && (this.removeFolds(p), p.forEach(function (e) { o.addSubFold(e) })); for (var d = 0; d < n.length; d++) { var v = n[d]; if (f == v.start.row) { v.addFold(o), r = !0; break } if (u == v.end.row) { v.addFold(o), r = !0; if (!o.sameRow) { var m = n[d + 1]; if (m && m.start.row == f) { v.merge(m); break } } break } if (f <= v.start.row) break } return r || (v = this.$addFoldLine(new i(this.$foldData, o))), this.$useWrapMode ? this.$updateWrapData(v.start.row, v.start.row) : this.$updateRowLengthCache(v.start.row, v.start.row), this.$modified = !0, this._signal("changeFold", { data: o, action: "add" }), o }, this.addFolds = function (e) { e.forEach(function (e) { this.addFold(e) }, this) }, this.removeFold = function (e) { var t = e.foldLine, n = t.start.row, r = t.end.row, i = this.$foldData, s = t.folds; if (s.length == 1) i.splice(i.indexOf(t), 1); else if (t.range.isEnd(e.end.row, e.end.column)) s.pop(), t.end.row = s[s.length - 1].end.row, t.end.column = s[s.length - 1].end.column; else if (t.range.isStart(e.start.row, e.start.column)) s.shift(), t.start.row = s[0].start.row, t.start.column = s[0].start.column; else if (e.sameRow) s.splice(s.indexOf(e), 1); else { var o = t.split(e.start.row, e.start.column); s = o.folds, s.shift(), o.start.row = s[0].start.row, o.start.column = s[0].start.column } this.$updating || (this.$useWrapMode ? this.$updateWrapData(n, r) : this.$updateRowLengthCache(n, r)), this.$modified = !0, this._signal("changeFold", { data: e, action: "remove" }) }, this.removeFolds = function (e) { var t = []; for (var n = 0; n < e.length; n++)t.push(e[n]); t.forEach(function (e) { this.removeFold(e) }, this), this.$modified = !0 }, this.expandFold = function (e) { this.removeFold(e), e.subFolds.forEach(function (t) { e.restoreRange(t), this.addFold(t) }, this), e.collapseChildren > 0 && this.foldAll(e.start.row + 1, e.end.row, e.collapseChildren - 1), e.subFolds = [] }, this.expandFolds = function (e) { e.forEach(function (e) { this.expandFold(e) }, this) }, this.unfold = function (e, t) { var n, i; e == null ? (n = new r(0, 0, this.getLength(), 0), t = !0) : typeof e == "number" ? n = new r(e, 0, e, this.getLine(e).length) : "row" in e ? n = r.fromPoints(e, e) : n = e, i = this.getFoldsInRangeList(n); if (t) this.removeFolds(i); else { var s = i; while (s.length) this.expandFolds(s), s = this.getFoldsInRangeList(n) } if (i.length) return i }, this.isRowFolded = function (e, t) { return !!this.getFoldLine(e, t) }, this.getRowFoldEnd = function (e, t) { var n = this.getFoldLine(e, t); return n ? n.end.row : e }, this.getRowFoldStart = function (e, t) { var n = this.getFoldLine(e, t); return n ? n.start.row : e }, this.getFoldDisplayLine = function (e, t, n, r, i) { r == null && (r = e.start.row), i == null && (i = 0), t == null && (t = e.end.row), n == null && (n = this.getLine(t).length); var s = this.doc, o = ""; return e.walk(function (e, t, n, u) { if (t < r) return; if (t == r) { if (n < i) return; u = Math.max(i, u) } e != null ? o += e : o += s.getLine(t).substring(u, n) }, t, n), o }, this.getDisplayLine = function (e, t, n, r) { var i = this.getFoldLine(e); if (!i) { var s; return s = this.doc.getLine(e), s.substring(r || 0, t || s.length) } return this.getFoldDisplayLine(i, e, t, n, r) }, this.$cloneFoldData = function () { var e = []; return e = this.$foldData.map(function (t) { var n = t.folds.map(function (e) { return e.clone() }); return new i(e, n) }), e }, this.toggleFold = function (e) { var t = this.selection, n = t.getRange(), r, i; if (n.isEmpty()) { var s = n.start; r = this.getFoldAt(s.row, s.column); if (r) { this.expandFold(r); return } (i = this.findMatchingBracket(s)) ? n.comparePoint(i) == 1 ? n.end = i : (n.start = i, n.start.column++, n.end.column--) : (i = this.findMatchingBracket({ row: s.row, column: s.column + 1 })) ? (n.comparePoint(i) == 1 ? n.end = i : n.start = i, n.start.column++) : n = this.getCommentFoldRange(s.row, s.column) || n } else { var o = this.getFoldsInRange(n); if (e && o.length) { this.expandFolds(o); return } o.length == 1 && (r = o[0]) } r || (r = this.getFoldAt(n.start.row, n.start.column)); if (r && r.range.toString() == n.toString()) { this.expandFold(r); return } var u = "..."; if (!n.isMultiLine()) { u = this.getTextRange(n); if (u.length < 4) return; u = u.trim().substring(0, 2) + ".." } this.addFold(u, n) }, this.getCommentFoldRange = function (e, t, n) { var i = new o(this, e, t), s = i.getCurrentToken(), u = s.type; if (s && /^comment|string/.test(u)) { u = u.match(/comment|string/)[0], u == "comment" && (u += "|doc-start"); var a = new RegExp(u), f = new r; if (n != 1) { do s = i.stepBackward(); while (s && a.test(s.type)); i.stepForward() } f.start.row = i.getCurrentTokenRow(), f.start.column = i.getCurrentTokenColumn() + 2, i = new o(this, e, t); if (n != -1) { var l = -1; do { s = i.stepForward(); if (l == -1) { var c = this.getState(i.$row); a.test(c) || (l = i.$row) } else if (i.$row > l) break } while (s && a.test(s.type)); s = i.stepBackward() } else s = i.getCurrentToken(); return f.end.row = i.getCurrentTokenRow(), f.end.column = i.getCurrentTokenColumn() + s.value.length - 2, f } }, this.foldAll = function (e, t, n) { n == undefined && (n = 1e5); var r = this.foldWidgets; if (!r) return; t = t || this.getLength(), e = e || 0; for (var i = e; i < t; i++) { r[i] == null && (r[i] = this.getFoldWidget(i)); if (r[i] != "start") continue; var s = this.getFoldWidgetRange(i); if (s && s.isMultiLine() && s.end.row <= t && s.start.row >= e) { i = s.end.row; try { var o = this.addFold("...", s); o && (o.collapseChildren = n) } catch (u) { } } } }, this.$foldStyles = { manual: 1, markbegin: 1, markbeginend: 1 }, this.$foldStyle = "markbegin", this.setFoldStyle = function (e) { if (!this.$foldStyles[e]) throw new Error("invalid fold style: " + e + "[" + Object.keys(this.$foldStyles).join(", ") + "]"); if (this.$foldStyle == e) return; this.$foldStyle = e, e == "manual" && this.unfold(); var t = this.$foldMode; this.$setFolding(null), this.$setFolding(t) }, this.$setFolding = function (e) { if (this.$foldMode == e) return; this.$foldMode = e, this.off("change", this.$updateFoldWidgets), this.off("tokenizerUpdate", this.$tokenizerUpdateFoldWidgets), this._signal("changeAnnotation"); if (!e || this.$foldStyle == "manual") { this.foldWidgets = null; return } this.foldWidgets = [], this.getFoldWidget = e.getFoldWidget.bind(e, this, this.$foldStyle), this.getFoldWidgetRange = e.getFoldWidgetRange.bind(e, this, this.$foldStyle), this.$updateFoldWidgets = this.updateFoldWidgets.bind(this), this.$tokenizerUpdateFoldWidgets = this.tokenizerUpdateFoldWidgets.bind(this), this.on("change", this.$updateFoldWidgets), this.on("tokenizerUpdate", this.$tokenizerUpdateFoldWidgets) }, this.getParentFoldRangeData = function (e, t) { var n = this.foldWidgets; if (!n || t && n[e]) return {}; var r = e - 1, i; while (r >= 0) { var s = n[r]; s == null && (s = n[r] = this.getFoldWidget(r)); if (s == "start") { var o = this.getFoldWidgetRange(r); i || (i = o); if (o && o.end.row >= e) break } r-- } return { range: r !== -1 && o, firstRange: i } }, this.onFoldWidgetClick = function (e, t) { t = t.domEvent; var n = { children: t.shiftKey, all: t.ctrlKey || t.metaKey, siblings: t.altKey }, r = this.$toggleFoldWidget(e, n); if (!r) { var i = t.target || t.srcElement; i && /ace_fold-widget/.test(i.className) && (i.className += " ace_invalid") } }, this.$toggleFoldWidget = function (e, t) { if (!this.getFoldWidget) return; var n = this.getFoldWidget(e), r = this.getLine(e), i = n === "end" ? -1 : 1, s = this.getFoldAt(e, i === -1 ? 0 : r.length, i); if (s) return t.children || t.all ? this.removeFold(s) : this.expandFold(s), s; var o = this.getFoldWidgetRange(e, !0); if (o && !o.isMultiLine()) { s = this.getFoldAt(o.start.row, o.start.column, 1); if (s && o.isEqual(s.range)) return this.removeFold(s), s } if (t.siblings) { var u = this.getParentFoldRangeData(e); if (u.range) var a = u.range.start.row + 1, f = u.range.end.row; this.foldAll(a, f, t.all ? 1e4 : 0) } else t.children ? (f = o ? o.end.row : this.getLength(), this.foldAll(e + 1, f, t.all ? 1e4 : 0)) : o && (t.all && (o.collapseChildren = 1e4), this.addFold("...", o)); return o }, this.toggleFoldWidget = function (e) { var t = this.selection.getCursor().row; t = this.getRowFoldStart(t); var n = this.$toggleFoldWidget(t, {}); if (n) return; var r = this.getParentFoldRangeData(t, !0); n = r.range || r.firstRange; if (n) { t = n.start.row; var i = this.getFoldAt(t, this.getLine(t).length, 1); i ? this.removeFold(i) : this.addFold("...", n) } }, this.updateFoldWidgets = function (e) { var t = e.start.row, n = e.end.row - t; if (n === 0) this.foldWidgets[t] = null; else if (e.action == "remove") this.foldWidgets.splice(t, n + 1, null); else { var r = Array(n + 1); r.unshift(t, 1), this.foldWidgets.splice.apply(this.foldWidgets, r) } }, this.tokenizerUpdateFoldWidgets = function (e) { var t = e.data; t.first != t.last && this.foldWidgets.length > t.first && this.foldWidgets.splice(t.first, this.foldWidgets.length) } } var r = e("../range").Range, i = e("./fold_line").FoldLine, s = e("./fold").Fold, o = e("../token_iterator").TokenIterator; t.Folding = u }), define("ace/edit_session/bracket_match", ["require", "exports", "module", "ace/token_iterator", "ace/range"], function (e, t, n) { "use strict"; function s() { this.findMatchingBracket = function (e, t) { if (e.column == 0) return null; var n = t || this.getLine(e.row).charAt(e.column - 1); if (n == "") return null; var r = n.match(/([\(\[\{])|([\)\]\}])/); return r ? r[1] ? this.$findClosingBracket(r[1], e) : this.$findOpeningBracket(r[2], e) : null }, this.getBracketRange = function (e) { var t = this.getLine(e.row), n = !0, r, s = t.charAt(e.column - 1), o = s && s.match(/([\(\[\{])|([\)\]\}])/); o || (s = t.charAt(e.column), e = { row: e.row, column: e.column + 1 }, o = s && s.match(/([\(\[\{])|([\)\]\}])/), n = !1); if (!o) return null; if (o[1]) { var u = this.$findClosingBracket(o[1], e); if (!u) return null; r = i.fromPoints(e, u), n || (r.end.column++, r.start.column--), r.cursor = r.end } else { var u = this.$findOpeningBracket(o[2], e); if (!u) return null; r = i.fromPoints(u, e), n || (r.start.column++, r.end.column--), r.cursor = r.start } return r }, this.getMatchingBracketRanges = function (e) { var t = this.getLine(e.row), n = t.charAt(e.column - 1), r = n && n.match(/([\(\[\{])|([\)\]\}])/); r || (n = t.charAt(e.column), e = { row: e.row, column: e.column + 1 }, r = n && n.match(/([\(\[\{])|([\)\]\}])/)); if (!r) return null; var s = new i(e.row, e.column - 1, e.row, e.column), o = r[1] ? this.$findClosingBracket(r[1], e) : this.$findOpeningBracket(r[2], e); if (!o) return [s]; var u = new i(o.row, o.column, o.row, o.column + 1); return [s, u] }, this.$brackets = { ")": "(", "(": ")", "]": "[", "[": "]", "{": "}", "}": "{", "<": ">", ">": "<" }, this.$findOpeningBracket = function (e, t, n) { var i = this.$brackets[e], s = 1, o = new r(this, t.row, t.column), u = o.getCurrentToken(); u || (u = o.stepForward()); if (!u) return; n || (n = new RegExp("(\\.?" + u.type.replace(".", "\\.").replace("rparen", ".paren").replace(/\b(?:end)\b/, "(?:start|begin|end)") + ")+")); var a = t.column - o.getCurrentTokenColumn() - 2, f = u.value; for (; ;) { while (a >= 0) { var l = f.charAt(a); if (l == i) { s -= 1; if (s == 0) return { row: o.getCurrentTokenRow(), column: a + o.getCurrentTokenColumn() } } else l == e && (s += 1); a -= 1 } do u = o.stepBackward(); while (u && !n.test(u.type)); if (u == null) break; f = u.value, a = f.length - 1 } return null }, this.$findClosingBracket = function (e, t, n) { var i = this.$brackets[e], s = 1, o = new r(this, t.row, t.column), u = o.getCurrentToken(); u || (u = o.stepForward()); if (!u) return; n || (n = new RegExp("(\\.?" + u.type.replace(".", "\\.").replace("lparen", ".paren").replace(/\b(?:start|begin)\b/, "(?:start|begin|end)") + ")+")); var a = t.column - o.getCurrentTokenColumn(); for (; ;) { var f = u.value, l = f.length; while (a < l) { var c = f.charAt(a); if (c == i) { s -= 1; if (s == 0) return { row: o.getCurrentTokenRow(), column: a + o.getCurrentTokenColumn() } } else c == e && (s += 1); a += 1 } do u = o.stepForward(); while (u && !n.test(u.type)); if (u == null) break; a = 0 } return null } } var r = e("../token_iterator").TokenIterator, i = e("../range").Range; t.BracketMatch = s }), define("ace/edit_session", ["require", "exports", "module", "ace/lib/oop", "ace/lib/lang", "ace/bidihandler", "ace/config", "ace/lib/event_emitter", "ace/selection", "ace/mode/text", "ace/range", "ace/document", "ace/background_tokenizer", "ace/search_highlight", "ace/edit_session/folding", "ace/edit_session/bracket_match"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/lang"), s = e("./bidihandler").BidiHandler, o = e("./config"), u = e("./lib/event_emitter").EventEmitter, a = e("./selection").Selection, f = e("./mode/text").Mode, l = e("./range").Range, c = e("./document").Document, h = e("./background_tokenizer").BackgroundTokenizer, p = e("./search_highlight").SearchHighlight, d = function (e, t) { this.$breakpoints = [], this.$decorations = [], this.$frontMarkers = {}, this.$backMarkers = {}, this.$markerId = 1, this.$undoSelect = !0, this.$foldData = [], this.id = "session" + ++d.$uid, this.$foldData.toString = function () { return this.join("\n") }, this.on("changeFold", this.onChangeFold.bind(this)), this.$onChange = this.onChange.bind(this); if (typeof e != "object" || !e.getLine) e = new c(e); this.setDocument(e), this.selection = new a(this), this.$bidiHandler = new s(this), o.resetOptions(this), this.setMode(t), o._signal("session", this) }; d.$uid = 0, function () { function m(e) { return e < 4352 ? !1 : e >= 4352 && e <= 4447 || e >= 4515 && e <= 4519 || e >= 4602 && e <= 4607 || e >= 9001 && e <= 9002 || e >= 11904 && e <= 11929 || e >= 11931 && e <= 12019 || e >= 12032 && e <= 12245 || e >= 12272 && e <= 12283 || e >= 12288 && e <= 12350 || e >= 12353 && e <= 12438 || e >= 12441 && e <= 12543 || e >= 12549 && e <= 12589 || e >= 12593 && e <= 12686 || e >= 12688 && e <= 12730 || e >= 12736 && e <= 12771 || e >= 12784 && e <= 12830 || e >= 12832 && e <= 12871 || e >= 12880 && e <= 13054 || e >= 13056 && e <= 19903 || e >= 19968 && e <= 42124 || e >= 42128 && e <= 42182 || e >= 43360 && e <= 43388 || e >= 44032 && e <= 55203 || e >= 55216 && e <= 55238 || e >= 55243 && e <= 55291 || e >= 63744 && e <= 64255 || e >= 65040 && e <= 65049 || e >= 65072 && e <= 65106 || e >= 65108 && e <= 65126 || e >= 65128 && e <= 65131 || e >= 65281 && e <= 65376 || e >= 65504 && e <= 65510 } r.implement(this, u), this.setDocument = function (e) { this.doc && this.doc.removeListener("change", this.$onChange), this.doc = e, e.on("change", this.$onChange), this.bgTokenizer && this.bgTokenizer.setDocument(this.getDocument()), this.resetCaches() }, this.getDocument = function () { return this.doc }, this.$resetRowCache = function (e) { if (!e) { this.$docRowCache = [], this.$screenRowCache = []; return } var t = this.$docRowCache.length, n = this.$getRowCacheIndex(this.$docRowCache, e) + 1; t > n && (this.$docRowCache.splice(n, t), this.$screenRowCache.splice(n, t)) }, this.$getRowCacheIndex = function (e, t) { var n = 0, r = e.length - 1; while (n <= r) { var i = n + r >> 1, s = e[i]; if (t > s) n = i + 1; else { if (!(t < s)) return i; r = i - 1 } } return n - 1 }, this.resetCaches = function () { this.$modified = !0, this.$wrapData = [], this.$rowLengthCache = [], this.$resetRowCache(0), this.bgTokenizer && this.bgTokenizer.start(0) }, this.onChangeFold = function (e) { var t = e.data; this.$resetRowCache(t.start.row) }, this.onChange = function (e) { this.$modified = !0, this.$bidiHandler.onChange(e), this.$resetRowCache(e.start.row); var t = this.$updateInternalDataOnChange(e); !this.$fromUndo && this.$undoManager && (t && t.length && (this.$undoManager.add({ action: "removeFolds", folds: t }, this.mergeUndoDeltas), this.mergeUndoDeltas = !0), this.$undoManager.add(e, this.mergeUndoDeltas), this.mergeUndoDeltas = !0, this.$informUndoManager.schedule()), this.bgTokenizer && this.bgTokenizer.$updateOnChange(e), this._signal("change", e) }, this.setValue = function (e) { this.doc.setValue(e), this.selection.moveTo(0, 0), this.$resetRowCache(0), this.setUndoManager(this.$undoManager), this.getUndoManager().reset() }, this.getValue = this.toString = function () { return this.doc.getValue() }, this.getSelection = function () { return this.selection }, this.getState = function (e) { return this.bgTokenizer.getState(e) }, this.getTokens = function (e) { return this.bgTokenizer.getTokens(e) }, this.getTokenAt = function (e, t) { var n = this.bgTokenizer.getTokens(e), r, i = 0; if (t == null) { var s = n.length - 1; i = this.getLine(e).length } else for (var s = 0; s < n.length; s++) { i += n[s].value.length; if (i >= t) break } return r = n[s], r ? (r.index = s, r.start = i - r.value.length, r) : null }, this.setUndoManager = function (e) { this.$undoManager = e, this.$informUndoManager && this.$informUndoManager.cancel(); if (e) { var t = this; e.addSession(this), this.$syncInformUndoManager = function () { t.$informUndoManager.cancel(), t.mergeUndoDeltas = !1 }, this.$informUndoManager = i.delayedCall(this.$syncInformUndoManager) } else this.$syncInformUndoManager = function () { } }, this.markUndoGroup = function () { this.$syncInformUndoManager && this.$syncInformUndoManager() }, this.$defaultUndoManager = { undo: function () { }, redo: function () { }, hasUndo: function () { }, hasRedo: function () { }, reset: function () { }, add: function () { }, addSelection: function () { }, startNewGroup: function () { }, addSession: function () { } }, this.getUndoManager = function () { return this.$undoManager || this.$defaultUndoManager }, this.getTabString = function () { return this.getUseSoftTabs() ? i.stringRepeat(" ", this.getTabSize()) : " " }, this.setUseSoftTabs = function (e) { this.setOption("useSoftTabs", e) }, this.getUseSoftTabs = function () { return this.$useSoftTabs && !this.$mode.$indentWithTabs }, this.setTabSize = function (e) { this.setOption("tabSize", e) }, this.getTabSize = function () { return this.$tabSize }, this.isTabStop = function (e) { return this.$useSoftTabs && e.column % this.$tabSize === 0 }, this.setNavigateWithinSoftTabs = function (e) { this.setOption("navigateWithinSoftTabs", e) }, this.getNavigateWithinSoftTabs = function () { return this.$navigateWithinSoftTabs }, this.$overwrite = !1, this.setOverwrite = function (e) { this.setOption("overwrite", e) }, this.getOverwrite = function () { return this.$overwrite }, this.toggleOverwrite = function () { this.setOverwrite(!this.$overwrite) }, this.addGutterDecoration = function (e, t) { this.$decorations[e] || (this.$decorations[e] = ""), this.$decorations[e] += " " + t, this._signal("changeBreakpoint", {}) }, this.removeGutterDecoration = function (e, t) { this.$decorations[e] = (this.$decorations[e] || "").replace(" " + t, ""), this._signal("changeBreakpoint", {}) }, this.getBreakpoints = function () { return this.$breakpoints }, this.setBreakpoints = function (e) { this.$breakpoints = []; for (var t = 0; t < e.length; t++)this.$breakpoints[e[t]] = "ace_breakpoint"; this._signal("changeBreakpoint", {}) }, this.clearBreakpoints = function () { this.$breakpoints = [], this._signal("changeBreakpoint", {}) }, this.setBreakpoint = function (e, t) { t === undefined && (t = "ace_breakpoint"), t ? this.$breakpoints[e] = t : delete this.$breakpoints[e], this._signal("changeBreakpoint", {}) }, this.clearBreakpoint = function (e) { delete this.$breakpoints[e], this._signal("changeBreakpoint", {}) }, this.addMarker = function (e, t, n, r) { var i = this.$markerId++, s = { range: e, type: n || "line", renderer: typeof n == "function" ? n : null, clazz: t, inFront: !!r, id: i }; return r ? (this.$frontMarkers[i] = s, this._signal("changeFrontMarker")) : (this.$backMarkers[i] = s, this._signal("changeBackMarker")), i }, this.addDynamicMarker = function (e, t) { if (!e.update) return; var n = this.$markerId++; return e.id = n, e.inFront = !!t, t ? (this.$frontMarkers[n] = e, this._signal("changeFrontMarker")) : (this.$backMarkers[n] = e, this._signal("changeBackMarker")), e }, this.removeMarker = function (e) { var t = this.$frontMarkers[e] || this.$backMarkers[e]; if (!t) return; var n = t.inFront ? this.$frontMarkers : this.$backMarkers; delete n[e], this._signal(t.inFront ? "changeFrontMarker" : "changeBackMarker") }, this.getMarkers = function (e) { return e ? this.$frontMarkers : this.$backMarkers }, this.highlight = function (e) { if (!this.$searchHighlight) { var t = new p(null, "ace_selected-word", "text"); this.$searchHighlight = this.addDynamicMarker(t) } this.$searchHighlight.setRegexp(e) }, this.highlightLines = function (e, t, n, r) { typeof t != "number" && (n = t, t = e), n || (n = "ace_step"); var i = new l(e, 0, t, Infinity); return i.id = this.addMarker(i, n, "fullLine", r), i }, this.setAnnotations = function (e) { this.$annotations = e, this._signal("changeAnnotation", {}) }, this.getAnnotations = function () { return this.$annotations || [] }, this.clearAnnotations = function () { this.setAnnotations([]) }, this.$detectNewLine = function (e) { var t = e.match(/^.*?(\r?\n)/m); t ? this.$autoNewLine = t[1] : this.$autoNewLine = "\n" }, this.getWordRange = function (e, t) { var n = this.getLine(e), r = !1; t > 0 && (r = !!n.charAt(t - 1).match(this.tokenRe)), r || (r = !!n.charAt(t).match(this.tokenRe)); if (r) var i = this.tokenRe; else if (/^\s+$/.test(n.slice(t - 1, t + 1))) var i = /\s/; else var i = this.nonTokenRe; var s = t; if (s > 0) { do s--; while (s >= 0 && n.charAt(s).match(i)); s++ } var o = t; while (o < n.length && n.charAt(o).match(i)) o++; return new l(e, s, e, o) }, this.getAWordRange = function (e, t) { var n = this.getWordRange(e, t), r = this.getLine(n.end.row); while (r.charAt(n.end.column).match(/[ \t]/)) n.end.column += 1; return n }, this.setNewLineMode = function (e) { this.doc.setNewLineMode(e) }, this.getNewLineMode = function () { return this.doc.getNewLineMode() }, this.setUseWorker = function (e) { this.setOption("useWorker", e) }, this.getUseWorker = function () { return this.$useWorker }, this.onReloadTokenizer = function (e) { var t = e.data; this.bgTokenizer.start(t.first), this._signal("tokenizerUpdate", e) }, this.$modes = o.$modes, this.$mode = null, this.$modeId = null, this.setMode = function (e, t) { if (e && typeof e == "object") { if (e.getTokenizer) return this.$onChangeMode(e); var n = e, r = n.path } else r = e || "ace/mode/text"; this.$modes["ace/mode/text"] || (this.$modes["ace/mode/text"] = new f); if (this.$modes[r] && !n) { this.$onChangeMode(this.$modes[r]), t && t(); return } this.$modeId = r, o.loadModule(["mode", r], function (e) { if (this.$modeId !== r) return t && t(); this.$modes[r] && !n ? this.$onChangeMode(this.$modes[r]) : e && e.Mode && (e = new e.Mode(n), n || (this.$modes[r] = e, e.$id = r), this.$onChangeMode(e)), t && t() }.bind(this)), this.$mode || this.$onChangeMode(this.$modes["ace/mode/text"], !0) }, this.$onChangeMode = function (e, t) { t || (this.$modeId = e.$id); if (this.$mode === e) return; var n = this.$mode; this.$mode = e, this.$stopWorker(), this.$useWorker && this.$startWorker(); var r = e.getTokenizer(); if (r.on !== undefined) { var i = this.onReloadTokenizer.bind(this); r.on("update", i) } if (!this.bgTokenizer) { this.bgTokenizer = new h(r); var s = this; this.bgTokenizer.on("update", function (e) { s._signal("tokenizerUpdate", e) }) } else this.bgTokenizer.setTokenizer(r); this.bgTokenizer.setDocument(this.getDocument()), this.tokenRe = e.tokenRe, this.nonTokenRe = e.nonTokenRe, t || (e.attachToSession && e.attachToSession(this), this.$options.wrapMethod.set.call(this, this.$wrapMethod), this.$setFolding(e.foldingRules), this.bgTokenizer.start(0), this._emit("changeMode", { oldMode: n, mode: e })) }, this.$stopWorker = function () { this.$worker && (this.$worker.terminate(), this.$worker = null) }, this.$startWorker = function () { try { this.$worker = this.$mode.createWorker(this) } catch (e) { o.warn("Could not load worker", e), this.$worker = null } }, this.getMode = function () { return this.$mode }, this.$scrollTop = 0, this.setScrollTop = function (e) { if (this.$scrollTop === e || isNaN(e)) return; this.$scrollTop = e, this._signal("changeScrollTop", e) }, this.getScrollTop = function () { return this.$scrollTop }, this.$scrollLeft = 0, this.setScrollLeft = function (e) { if (this.$scrollLeft === e || isNaN(e)) return; this.$scrollLeft = e, this._signal("changeScrollLeft", e) }, this.getScrollLeft = function () { return this.$scrollLeft }, this.getScreenWidth = function () { return this.$computeWidth(), this.lineWidgets ? Math.max(this.getLineWidgetMaxWidth(), this.screenWidth) : this.screenWidth }, this.getLineWidgetMaxWidth = function () { if (this.lineWidgetsWidth != null) return this.lineWidgetsWidth; var e = 0; return this.lineWidgets.forEach(function (t) { t && t.screenWidth > e && (e = t.screenWidth) }), this.lineWidgetWidth = e }, this.$computeWidth = function (e) { if (this.$modified || e) { this.$modified = !1; if (this.$useWrapMode) return this.screenWidth = this.$wrapLimit; var t = this.doc.getAllLines(), n = this.$rowLengthCache, r = 0, i = 0, s = this.$foldData[i], o = s ? s.start.row : Infinity, u = t.length; for (var a = 0; a < u; a++) { if (a > o) { a = s.end.row + 1; if (a >= u) break; s = this.$foldData[i++], o = s ? s.start.row : Infinity } n[a] == null && (n[a] = this.$getStringScreenWidth(t[a])[0]), n[a] > r && (r = n[a]) } this.screenWidth = r } }, this.getLine = function (e) { return this.doc.getLine(e) }, this.getLines = function (e, t) { return this.doc.getLines(e, t) }, this.getLength = function () { return this.doc.getLength() }, this.getTextRange = function (e) { return this.doc.getTextRange(e || this.selection.getRange()) }, this.insert = function (e, t) { return this.doc.insert(e, t) }, this.remove = function (e) { return this.doc.remove(e) }, this.removeFullLines = function (e, t) { return this.doc.removeFullLines(e, t) }, this.undoChanges = function (e, t) { if (!e.length) return; this.$fromUndo = !0; for (var n = e.length - 1; n != -1; n--) { var r = e[n]; r.action == "insert" || r.action == "remove" ? this.doc.revertDelta(r) : r.folds && this.addFolds(r.folds) } !t && this.$undoSelect && (e.selectionBefore ? this.selection.fromJSON(e.selectionBefore) : this.selection.setRange(this.$getUndoSelection(e, !0))), this.$fromUndo = !1 }, this.redoChanges = function (e, t) { if (!e.length) return; this.$fromUndo = !0; for (var n = 0; n < e.length; n++) { var r = e[n]; (r.action == "insert" || r.action == "remove") && this.doc.$safeApplyDelta(r) } !t && this.$undoSelect && (e.selectionAfter ? this.selection.fromJSON(e.selectionAfter) : this.selection.setRange(this.$getUndoSelection(e, !1))), this.$fromUndo = !1 }, this.setUndoSelect = function (e) { this.$undoSelect = e }, this.$getUndoSelection = function (e, t) { function n(e) { return t ? e.action !== "insert" : e.action === "insert" } var r, i; for (var s = 0; s < e.length; s++) { var o = e[s]; if (!o.start) continue; if (!r) { n(o) ? r = l.fromPoints(o.start, o.end) : r = l.fromPoints(o.start, o.start); continue } n(o) ? (i = o.start, r.compare(i.row, i.column) == -1 && r.setStart(i), i = o.end, r.compare(i.row, i.column) == 1 && r.setEnd(i)) : (i = o.start, r.compare(i.row, i.column) == -1 && (r = l.fromPoints(o.start, o.start))) } return r }, this.replace = function (e, t) { return this.doc.replace(e, t) }, this.moveText = function (e, t, n) { var r = this.getTextRange(e), i = this.getFoldsInRange(e), s = l.fromPoints(t, t); if (!n) { this.remove(e); var o = e.start.row - e.end.row, u = o ? -e.end.column : e.start.column - e.end.column; u && (s.start.row == e.end.row && s.start.column > e.end.column && (s.start.column += u), s.end.row == e.end.row && s.end.column > e.end.column && (s.end.column += u)), o && s.start.row >= e.end.row && (s.start.row += o, s.end.row += o) } s.end = this.insert(s.start, r); if (i.length) { var a = e.start, f = s.start, o = f.row - a.row, u = f.column - a.column; this.addFolds(i.map(function (e) { return e = e.clone(), e.start.row == a.row && (e.start.column += u), e.end.row == a.row && (e.end.column += u), e.start.row += o, e.end.row += o, e })) } return s }, this.indentRows = function (e, t, n) { n = n.replace(/\t/g, this.getTabString()); for (var r = e; r <= t; r++)this.doc.insertInLine({ row: r, column: 0 }, n) }, this.outdentRows = function (e) { var t = e.collapseRows(), n = new l(0, 0, 0, 0), r = this.getTabSize(); for (var i = t.start.row; i <= t.end.row; ++i) { var s = this.getLine(i); n.start.row = i, n.end.row = i; for (var o = 0; o < r; ++o)if (s.charAt(o) != " ") break; o < r && s.charAt(o) == " " ? (n.start.column = o, n.end.column = o + 1) : (n.start.column = 0, n.end.column = o), this.remove(n) } }, this.$moveLines = function (e, t, n) { e = this.getRowFoldStart(e), t = this.getRowFoldEnd(t); if (n < 0) { var r = this.getRowFoldStart(e + n); if (r < 0) return 0; var i = r - e } else if (n > 0) { var r = this.getRowFoldEnd(t + n); if (r > this.doc.getLength() - 1) return 0; var i = r - t } else { e = this.$clipRowToDocument(e), t = this.$clipRowToDocument(t); var i = t - e + 1 } var s = new l(e, 0, t, Number.MAX_VALUE), o = this.getFoldsInRange(s).map(function (e) { return e = e.clone(), e.start.row += i, e.end.row += i, e }), u = n == 0 ? this.doc.getLines(e, t) : this.doc.removeFullLines(e, t); return this.doc.insertFullLines(e + i, u), o.length && this.addFolds(o), i }, this.moveLinesUp = function (e, t) { return this.$moveLines(e, t, -1) }, this.moveLinesDown = function (e, t) { return this.$moveLines(e, t, 1) }, this.duplicateLines = function (e, t) { return this.$moveLines(e, t, 0) }, this.$clipRowToDocument = function (e) { return Math.max(0, Math.min(e, this.doc.getLength() - 1)) }, this.$clipColumnToRow = function (e, t) { return t < 0 ? 0 : Math.min(this.doc.getLine(e).length, t) }, this.$clipPositionToDocument = function (e, t) { t = Math.max(0, t); if (e < 0) e = 0, t = 0; else { var n = this.doc.getLength(); e >= n ? (e = n - 1, t = this.doc.getLine(n - 1).length) : t = Math.min(this.doc.getLine(e).length, t) } return { row: e, column: t } }, this.$clipRangeToDocument = function (e) { e.start.row < 0 ? (e.start.row = 0, e.start.column = 0) : e.start.column = this.$clipColumnToRow(e.start.row, e.start.column); var t = this.doc.getLength() - 1; return e.end.row > t ? (e.end.row = t, e.end.column = this.doc.getLine(t).length) : e.end.column = this.$clipColumnToRow(e.end.row, e.end.column), e }, this.$wrapLimit = 80, this.$useWrapMode = !1, this.$wrapLimitRange = { min: null, max: null }, this.setUseWrapMode = function (e) { if (e != this.$useWrapMode) { this.$useWrapMode = e, this.$modified = !0, this.$resetRowCache(0); if (e) { var t = this.getLength(); this.$wrapData = Array(t), this.$updateWrapData(0, t - 1) } this._signal("changeWrapMode") } }, this.getUseWrapMode = function () { return this.$useWrapMode }, this.setWrapLimitRange = function (e, t) { if (this.$wrapLimitRange.min !== e || this.$wrapLimitRange.max !== t) this.$wrapLimitRange = { min: e, max: t }, this.$modified = !0, this.$bidiHandler.markAsDirty(), this.$useWrapMode && this._signal("changeWrapMode") }, this.adjustWrapLimit = function (e, t) { var n = this.$wrapLimitRange; n.max < 0 && (n = { min: t, max: t }); var r = this.$constrainWrapLimit(e, n.min, n.max); return r != this.$wrapLimit && r > 1 ? (this.$wrapLimit = r, this.$modified = !0, this.$useWrapMode && (this.$updateWrapData(0, this.getLength() - 1), this.$resetRowCache(0), this._signal("changeWrapLimit")), !0) : !1 }, this.$constrainWrapLimit = function (e, t, n) { return t && (e = Math.max(t, e)), n && (e = Math.min(n, e)), e }, this.getWrapLimit = function () { return this.$wrapLimit }, this.setWrapLimit = function (e) { this.setWrapLimitRange(e, e) }, this.getWrapLimitRange = function () { return { min: this.$wrapLimitRange.min, max: this.$wrapLimitRange.max } }, this.$updateInternalDataOnChange = function (e) { var t = this.$useWrapMode, n = e.action, r = e.start, i = e.end, s = r.row, o = i.row, u = o - s, a = null; this.$updating = !0; if (u != 0) if (n === "remove") { this[t ? "$wrapData" : "$rowLengthCache"].splice(s, u); var f = this.$foldData; a = this.getFoldsInRange(e), this.removeFolds(a); var l = this.getFoldLine(i.row), c = 0; if (l) { l.addRemoveChars(i.row, i.column, r.column - i.column), l.shiftRow(-u); var h = this.getFoldLine(s); h && h !== l && (h.merge(l), l = h), c = f.indexOf(l) + 1 } for (c; c < f.length; c++) { var l = f[c]; l.start.row >= i.row && l.shiftRow(-u) } o = s } else { var p = Array(u); p.unshift(s, 0); var d = t ? this.$wrapData : this.$rowLengthCache; d.splice.apply(d, p); var f = this.$foldData, l = this.getFoldLine(s), c = 0; if (l) { var v = l.range.compareInside(r.row, r.column); v == 0 ? (l = l.split(r.row, r.column), l && (l.shiftRow(u), l.addRemoveChars(o, 0, i.column - r.column))) : v == -1 && (l.addRemoveChars(s, 0, i.column - r.column), l.shiftRow(u)), c = f.indexOf(l) + 1 } for (c; c < f.length; c++) { var l = f[c]; l.start.row >= s && l.shiftRow(u) } } else { u = Math.abs(e.start.column - e.end.column), n === "remove" && (a = this.getFoldsInRange(e), this.removeFolds(a), u = -u); var l = this.getFoldLine(s); l && l.addRemoveChars(s, r.column, u) } return t && this.$wrapData.length != this.doc.getLength() && console.error("doc.getLength() and $wrapData.length have to be the same!"), this.$updating = !1, t ? this.$updateWrapData(s, o) : this.$updateRowLengthCache(s, o), a }, this.$updateRowLengthCache = function (e, t, n) { this.$rowLengthCache[e] = null, this.$rowLengthCache[t] = null }, this.$updateWrapData = function (e, t) { var r = this.doc.getAllLines(), i = this.getTabSize(), o = this.$wrapData, u = this.$wrapLimit, a, f, l = e; t = Math.min(t, r.length - 1); while (l <= t) f = this.getFoldLine(l, f), f ? (a = [], f.walk(function (e, t, i, o) { var u; if (e != null) { u = this.$getDisplayTokens(e, a.length), u[0] = n; for (var f = 1; f < u.length; f++)u[f] = s } else u = this.$getDisplayTokens(r[t].substring(o, i), a.length); a = a.concat(u) }.bind(this), f.end.row, r[f.end.row].length + 1), o[f.start.row] = this.$computeWrapSplits(a, u, i), l = f.end.row + 1) : (a = this.$getDisplayTokens(r[l]), o[l] = this.$computeWrapSplits(a, u, i), l++) }; var e = 1, t = 2, n = 3, s = 4, a = 9, c = 10, d = 11, v = 12; this.$computeWrapSplits = function (e, r, i) { function g() { var t = 0; if (m === 0) return t; if (p) for (var n = 0; n < e.length; n++) { var r = e[n]; if (r == c) t += 1; else { if (r != d) { if (r == v) continue; break } t += i } } return h && p !== !1 && (t += i), Math.min(t, m) } function y(t) { var n = t - f; for (var r = f; r < t; r++) { var i = e[r]; if (i === 12 || i === 2) n -= 1 } o.length || (b = g(), o.indent = b), l += n, o.push(l), f = t } if (e.length == 0) return []; var o = [], u = e.length, f = 0, l = 0, h = this.$wrapAsCode, p = this.$indentedSoftWrap, m = r <= Math.max(2 * i, 8) || p === !1 ? 0 : Math.floor(r / 2), b = 0; while (u - f > r - b) { var w = f + r - b; if (e[w - 1] >= c && e[w] >= c) { y(w); continue } if (e[w] == n || e[w] == s) { for (w; w != f - 1; w--)if (e[w] == n) break; if (w > f) { y(w); continue } w = f + r; for (w; w < e.length; w++)if (e[w] != s) break; if (w == e.length) break; y(w); continue } var E = Math.max(w - (r - (r >> 2)), f - 1); while (w > E && e[w] < n) w--; if (h) { while (w > E && e[w] < n) w--; while (w > E && e[w] == a) w-- } else while (w > E && e[w] < c) w--; if (w > E) { y(++w); continue } w = f + r, e[w] == t && w--, y(w - b) } return o }, this.$getDisplayTokens = function (n, r) { var i = [], s; r = r || 0; for (var o = 0; o < n.length; o++) { var u = n.charCodeAt(o); if (u == 9) { s = this.getScreenTabSize(i.length + r), i.push(d); for (var f = 1; f < s; f++)i.push(v) } else u == 32 ? i.push(c) : u > 39 && u < 48 || u > 57 && u < 64 ? i.push(a) : u >= 4352 && m(u) ? i.push(e, t) : i.push(e) } return i }, this.$getStringScreenWidth = function (e, t, n) { if (t == 0) return [0, 0]; t == null && (t = Infinity), n = n || 0; var r, i; for (i = 0; i < e.length; i++) { r = e.charCodeAt(i), r == 9 ? n += this.getScreenTabSize(n) : r >= 4352 && m(r) ? n += 2 : n += 1; if (n > t) break } return [n, i] }, this.lineWidgets = null, this.getRowLength = function (e) { var t = 1; return this.lineWidgets && (t += this.lineWidgets[e] && this.lineWidgets[e].rowCount || 0), !this.$useWrapMode || !this.$wrapData[e] ? t : this.$wrapData[e].length + t }, this.getRowLineCount = function (e) { return !this.$useWrapMode || !this.$wrapData[e] ? 1 : this.$wrapData[e].length + 1 }, this.getRowWrapIndent = function (e) { if (this.$useWrapMode) { var t = this.screenToDocumentPosition(e, Number.MAX_VALUE), n = this.$wrapData[t.row]; return n.length && n[0] < t.column ? n.indent : 0 } return 0 }, this.getScreenLastRowColumn = function (e) { var t = this.screenToDocumentPosition(e, Number.MAX_VALUE); return this.documentToScreenColumn(t.row, t.column) }, this.getDocumentLastRowColumn = function (e, t) { var n = this.documentToScreenRow(e, t); return this.getScreenLastRowColumn(n) }, this.getDocumentLastRowColumnPosition = function (e, t) { var n = this.documentToScreenRow(e, t); return this.screenToDocumentPosition(n, Number.MAX_VALUE / 10) }, this.getRowSplitData = function (e) { return this.$useWrapMode ? this.$wrapData[e] : undefined }, this.getScreenTabSize = function (e) { return this.$tabSize - (e % this.$tabSize | 0) }, this.screenToDocumentRow = function (e, t) { return this.screenToDocumentPosition(e, t).row }, this.screenToDocumentColumn = function (e, t) { return this.screenToDocumentPosition(e, t).column }, this.screenToDocumentPosition = function (e, t, n) { if (e < 0) return { row: 0, column: 0 }; var r, i = 0, s = 0, o, u = 0, a = 0, f = this.$screenRowCache, l = this.$getRowCacheIndex(f, e), c = f.length; if (c && l >= 0) var u = f[l], i = this.$docRowCache[l], h = e > f[c - 1]; else var h = !c; var p = this.getLength() - 1, d = this.getNextFoldLine(i), v = d ? d.start.row : Infinity; while (u <= e) { a = this.getRowLength(i); if (u + a > e || i >= p) break; u += a, i++, i > v && (i = d.end.row + 1, d = this.getNextFoldLine(i, d), v = d ? d.start.row : Infinity), h && (this.$docRowCache.push(i), this.$screenRowCache.push(u)) } if (d && d.start.row <= i) r = this.getFoldDisplayLine(d), i = d.start.row; else { if (u + a <= e || i > p) return { row: p, column: this.getLine(p).length }; r = this.getLine(i), d = null } var m = 0, g = Math.floor(e - u); if (this.$useWrapMode) { var y = this.$wrapData[i]; y && (o = y[g], g > 0 && y.length && (m = y.indent, s = y[g - 1] || y[y.length - 1], r = r.substring(s))) } return n !== undefined && this.$bidiHandler.isBidiRow(u + g, i, g) && (t = this.$bidiHandler.offsetToCol(n)), s += this.$getStringScreenWidth(r, t - m)[1], this.$useWrapMode && s >= o && (s = o - 1), d ? d.idxToPosition(s) : { row: i, column: s } }, this.documentToScreenPosition = function (e, t) { if (typeof t == "undefined") var n = this.$clipPositionToDocument(e.row, e.column); else n = this.$clipPositionToDocument(e, t); e = n.row, t = n.column; var r = 0, i = null, s = null; s = this.getFoldAt(e, t, 1), s && (e = s.start.row, t = s.start.column); var o, u = 0, a = this.$docRowCache, f = this.$getRowCacheIndex(a, e), l = a.length; if (l && f >= 0) var u = a[f], r = this.$screenRowCache[f], c = e > a[l - 1]; else var c = !l; var h = this.getNextFoldLine(u), p = h ? h.start.row : Infinity; while (u < e) { if (u >= p) { o = h.end.row + 1; if (o > e) break; h = this.getNextFoldLine(o, h), p = h ? h.start.row : Infinity } else o = u + 1; r += this.getRowLength(u), u = o, c && (this.$docRowCache.push(u), this.$screenRowCache.push(r)) } var d = ""; h && u >= p ? (d = this.getFoldDisplayLine(h, e, t), i = h.start.row) : (d = this.getLine(e).substring(0, t), i = e); var v = 0; if (this.$useWrapMode) { var m = this.$wrapData[i]; if (m) { var g = 0; while (d.length >= m[g]) r++, g++; d = d.substring(m[g - 1] || 0, d.length), v = g > 0 ? m.indent : 0 } } return this.lineWidgets && this.lineWidgets[u] && this.lineWidgets[u].rowsAbove && (r += this.lineWidgets[u].rowsAbove), { row: r, column: v + this.$getStringScreenWidth(d)[0] } }, this.documentToScreenColumn = function (e, t) { return this.documentToScreenPosition(e, t).column }, this.documentToScreenRow = function (e, t) { return this.documentToScreenPosition(e, t).row }, this.getScreenLength = function () { var e = 0, t = null; if (!this.$useWrapMode) { e = this.getLength(); var n = this.$foldData; for (var r = 0; r < n.length; r++)t = n[r], e -= t.end.row - t.start.row } else { var i = this.$wrapData.length, s = 0, r = 0, t = this.$foldData[r++], o = t ? t.start.row : Infinity; while (s < i) { var u = this.$wrapData[s]; e += u ? u.length + 1 : 1, s++, s > o && (s = t.end.row + 1, t = this.$foldData[r++], o = t ? t.start.row : Infinity) } } return this.lineWidgets && (e += this.$getWidgetScreenLength()), e }, this.$setFontMetrics = function (e) { if (!this.$enableVarChar) return; this.$getStringScreenWidth = function (t, n, r) { if (n === 0) return [0, 0]; n || (n = Infinity), r = r || 0; var i, s; for (s = 0; s < t.length; s++) { i = t.charAt(s), i === " " ? r += this.getScreenTabSize(r) : r += e.getCharacterWidth(i); if (r > n) break } return [r, s] } }, this.destroy = function () { this.bgTokenizer && (this.bgTokenizer.setDocument(null), this.bgTokenizer = null), this.$stopWorker(), this.removeAllListeners(), this.selection.detach() }, this.isFullWidth = m }.call(d.prototype), e("./edit_session/folding").Folding.call(d.prototype), e("./edit_session/bracket_match").BracketMatch.call(d.prototype), o.defineOptions(d.prototype, "session", { wrap: { set: function (e) { !e || e == "off" ? e = !1 : e == "free" ? e = !0 : e == "printMargin" ? e = -1 : typeof e == "string" && (e = parseInt(e, 10) || !1); if (this.$wrap == e) return; this.$wrap = e; if (!e) this.setUseWrapMode(!1); else { var t = typeof e == "number" ? e : null; this.setWrapLimitRange(t, t), this.setUseWrapMode(!0) } }, get: function () { return this.getUseWrapMode() ? this.$wrap == -1 ? "printMargin" : this.getWrapLimitRange().min ? this.$wrap : "free" : "off" }, handlesSet: !0 }, wrapMethod: { set: function (e) { e = e == "auto" ? this.$mode.type != "text" : e != "text", e != this.$wrapAsCode && (this.$wrapAsCode = e, this.$useWrapMode && (this.$useWrapMode = !1, this.setUseWrapMode(!0))) }, initialValue: "auto" }, indentedSoftWrap: { set: function () { this.$useWrapMode && (this.$useWrapMode = !1, this.setUseWrapMode(!0)) }, initialValue: !0 }, firstLineNumber: { set: function () { this._signal("changeBreakpoint") }, initialValue: 1 }, useWorker: { set: function (e) { this.$useWorker = e, this.$stopWorker(), e && this.$startWorker() }, initialValue: !0 }, useSoftTabs: { initialValue: !0 }, tabSize: { set: function (e) { e = parseInt(e), e > 0 && this.$tabSize !== e && (this.$modified = !0, this.$rowLengthCache = [], this.$tabSize = e, this._signal("changeTabSize")) }, initialValue: 4, handlesSet: !0 }, navigateWithinSoftTabs: { initialValue: !1 }, foldStyle: { set: function (e) { this.setFoldStyle(e) }, handlesSet: !0 }, overwrite: { set: function (e) { this._signal("changeOverwrite") }, initialValue: !1 }, newLineMode: { set: function (e) { this.doc.setNewLineMode(e) }, get: function () { return this.doc.getNewLineMode() }, handlesSet: !0 }, mode: { set: function (e) { this.setMode(e) }, get: function () { return this.$modeId }, handlesSet: !0 } }), t.EditSession = d }), define("ace/search", ["require", "exports", "module", "ace/lib/lang", "ace/lib/oop", "ace/range"], function (e, t, n) { "use strict"; function u(e, t) { function n(e) { return /\w/.test(e) || t.regExp ? "\\b" : "" } return n(e[0]) + e + n(e[e.length - 1]) } var r = e("./lib/lang"), i = e("./lib/oop"), s = e("./range").Range, o = function () { this.$options = {} }; (function () { this.set = function (e) { return i.mixin(this.$options, e), this }, this.getOptions = function () { return r.copyObject(this.$options) }, this.setOptions = function (e) { this.$options = e }, this.find = function (e) { var t = this.$options, n = this.$matchIterator(e, t); if (!n) return !1; var r = null; return n.forEach(function (e, n, i, o) { return r = new s(e, n, i, o), n == o && t.start && t.start.start && t.skipCurrent != 0 && r.isEqual(t.start) ? (r = null, !1) : !0 }), r }, this.findAll = function (e) { var t = this.$options; if (!t.needle) return []; this.$assembleRegExp(t); var n = t.range, i = n ? e.getLines(n.start.row, n.end.row) : e.doc.getAllLines(), o = [], u = t.re; if (t.$isMultiLine) { var a = u.length, f = i.length - a, l; e: for (var c = u.offset || 0; c <= f; c++) { for (var h = 0; h < a; h++)if (i[c + h].search(u[h]) == -1) continue e; var p = i[c], d = i[c + a - 1], v = p.length - p.match(u[0])[0].length, m = d.match(u[a - 1])[0].length; if (l && l.end.row === c && l.end.column > v) continue; o.push(l = new s(c, v, c + a - 1, m)), a > 2 && (c = c + a - 2) } } else for (var g = 0; g < i.length; g++) { var y = r.getMatchOffsets(i[g], u); for (var h = 0; h < y.length; h++) { var b = y[h]; o.push(new s(g, b.offset, g, b.offset + b.length)) } } if (n) { var w = n.start.column, E = n.start.column, g = 0, h = o.length - 1; while (g < h && o[g].start.column < w && o[g].start.row == n.start.row) g++; while (g < h && o[h].end.column > E && o[h].end.row == n.end.row) h--; o = o.slice(g, h + 1); for (g = 0, h = o.length; g < h; g++)o[g].start.row += n.start.row, o[g].end.row += n.start.row } return o }, this.replace = function (e, t) { var n = this.$options, r = this.$assembleRegExp(n); if (n.$isMultiLine) return t; if (!r) return; var i = r.exec(e); if (!i || i[0].length != e.length) return null; t = e.replace(r, t); if (n.preserveCase) { t = t.split(""); for (var s = Math.min(e.length, e.length); s--;) { var o = e[s]; o && o.toLowerCase() != o ? t[s] = t[s].toUpperCase() : t[s] = t[s].toLowerCase() } t = t.join("") } return t }, this.$assembleRegExp = function (e, t) { if (e.needle instanceof RegExp) return e.re = e.needle; var n = e.needle; if (!e.needle) return e.re = !1; e.regExp || (n = r.escapeRegExp(n)), e.wholeWord && (n = u(n, e)); var i = e.caseSensitive ? "gm" : "gmi"; e.$isMultiLine = !t && /[\n\r]/.test(n); if (e.$isMultiLine) return e.re = this.$assembleMultilineRegExp(n, i); try { var s = new RegExp(n, i) } catch (o) { s = !1 } return e.re = s }, this.$assembleMultilineRegExp = function (e, t) { var n = e.replace(/\r\n|\r|\n/g, "$\n^").split("\n"), r = []; for (var i = 0; i < n.length; i++)try { r.push(new RegExp(n[i], t)) } catch (s) { return !1 } return r }, this.$matchIterator = function (e, t) { var n = this.$assembleRegExp(t); if (!n) return !1; var r = t.backwards == 1, i = t.skipCurrent != 0, s = t.range, o = t.start; o || (o = s ? s[r ? "end" : "start"] : e.selection.getRange()), o.start && (o = o[i != r ? "end" : "start"]); var u = s ? s.start.row : 0, a = s ? s.end.row : e.getLength() - 1; if (r) var f = function (e) { var n = o.row; if (c(n, o.column, e)) return; for (n--; n >= u; n--)if (c(n, Number.MAX_VALUE, e)) return; if (t.wrap == 0) return; for (n = a, u = o.row; n >= u; n--)if (c(n, Number.MAX_VALUE, e)) return }; else var f = function (e) { var n = o.row; if (c(n, o.column, e)) return; for (n += 1; n <= a; n++)if (c(n, 0, e)) return; if (t.wrap == 0) return; for (n = u, a = o.row; n <= a; n++)if (c(n, 0, e)) return }; if (t.$isMultiLine) var l = n.length, c = function (t, i, s) { var o = r ? t - l + 1 : t; if (o < 0) return; var u = e.getLine(o), a = u.search(n[0]); if (!r && a < i || a === -1) return; for (var f = 1; f < l; f++) { u = e.getLine(o + f); if (u.search(n[f]) == -1) return } var c = u.match(n[l - 1])[0].length; if (r && c > i) return; if (s(o, a, o + l - 1, c)) return !0 }; else if (r) var c = function (t, r, i) { var s = e.getLine(t), o = [], u, a = 0; n.lastIndex = 0; while (u = n.exec(s)) { var f = u[0].length; a = u.index; if (!f) { if (a >= s.length) break; n.lastIndex = a += 1 } if (u.index + f > r) break; o.push(u.index, f) } for (var l = o.length - 1; l >= 0; l -= 2) { var c = o[l - 1], f = o[l]; if (i(t, c, t, c + f)) return !0 } }; else var c = function (t, r, i) { var s = e.getLine(t), o, u; n.lastIndex = r; while (u = n.exec(s)) { var a = u[0].length; o = u.index; if (i(t, o, t, o + a)) return !0; if (!a) { n.lastIndex = o += 1; if (o >= s.length) return !1 } } }; return { forEach: f } } }).call(o.prototype), t.Search = o }), define("ace/keyboard/hash_handler", ["require", "exports", "module", "ace/lib/keys", "ace/lib/useragent"], function (e, t, n) { "use strict"; function o(e, t) { this.platform = t || (i.isMac ? "mac" : "win"), this.commands = {}, this.commandKeyBinding = {}, this.addCommands(e), this.$singleCommand = !0 } function u(e, t) { o.call(this, e, t), this.$singleCommand = !1 } var r = e("../lib/keys"), i = e("../lib/useragent"), s = r.KEY_MODS; u.prototype = o.prototype, function () { function e(e) { return typeof e == "object" && e.bindKey && e.bindKey.position || (e.isDefault ? -100 : 0) } this.addCommand = function (e) { this.commands[e.name] && this.removeCommand(e), this.commands[e.name] = e, e.bindKey && this._buildKeyHash(e) }, this.removeCommand = function (e, t) { var n = e && (typeof e == "string" ? e : e.name); e = this.commands[n], t || delete this.commands[n]; var r = this.commandKeyBinding; for (var i in r) { var s = r[i]; if (s == e) delete r[i]; else if (Array.isArray(s)) { var o = s.indexOf(e); o != -1 && (s.splice(o, 1), s.length == 1 && (r[i] = s[0])) } } }, this.bindKey = function (e, t, n) { typeof e == "object" && e && (n == undefined && (n = e.position), e = e[this.platform]); if (!e) return; if (typeof t == "function") return this.addCommand({ exec: t, bindKey: e, name: t.name || e }); e.split("|").forEach(function (e) { var r = ""; if (e.indexOf(" ") != -1) { var i = e.split(/\s+/); e = i.pop(), i.forEach(function (e) { var t = this.parseKeys(e), n = s[t.hashId] + t.key; r += (r ? " " : "") + n, this._addCommandToBinding(r, "chainKeys") }, this), r += " " } var o = this.parseKeys(e), u = s[o.hashId] + o.key; this._addCommandToBinding(r + u, t, n) }, this) }, this._addCommandToBinding = function (t, n, r) { var i = this.commandKeyBinding, s; if (!n) delete i[t]; else if (!i[t] || this.$singleCommand) i[t] = n; else { Array.isArray(i[t]) ? (s = i[t].indexOf(n)) != -1 && i[t].splice(s, 1) : i[t] = [i[t]], typeof r != "number" && (r = e(n)); var o = i[t]; for (s = 0; s < o.length; s++) { var u = o[s], a = e(u); if (a > r) break } o.splice(s, 0, n) } }, this.addCommands = function (e) { e && Object.keys(e).forEach(function (t) { var n = e[t]; if (!n) return; if (typeof n == "string") return this.bindKey(n, t); typeof n == "function" && (n = { exec: n }); if (typeof n != "object") return; n.name || (n.name = t), this.addCommand(n) }, this) }, this.removeCommands = function (e) { Object.keys(e).forEach(function (t) { this.removeCommand(e[t]) }, this) }, this.bindKeys = function (e) { Object.keys(e).forEach(function (t) { this.bindKey(t, e[t]) }, this) }, this._buildKeyHash = function (e) { this.bindKey(e.bindKey, e) }, this.parseKeys = function (e) { var t = e.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function (e) { return e }), n = t.pop(), i = r[n]; if (r.FUNCTION_KEYS[i]) n = r.FUNCTION_KEYS[i].toLowerCase(); else { if (!t.length) return { key: n, hashId: -1 }; if (t.length == 1 && t[0] == "shift") return { key: n.toUpperCase(), hashId: -1 } } var s = 0; for (var o = t.length; o--;) { var u = r.KEY_MODS[t[o]]; if (u == null) return typeof console != "undefined" && console.error("invalid modifier " + t[o] + " in " + e), !1; s |= u } return { key: n, hashId: s } }, this.findKeyCommand = function (t, n) { var r = s[t] + n; return this.commandKeyBinding[r] }, this.handleKeyboard = function (e, t, n, r) { if (r < 0) return; var i = s[t] + n, o = this.commandKeyBinding[i]; e.$keyChain && (e.$keyChain += " " + i, o = this.commandKeyBinding[e.$keyChain] || o); if (o) if (o == "chainKeys" || o[o.length - 1] == "chainKeys") return e.$keyChain = e.$keyChain || i, { command: "null" }; if (e.$keyChain) if (!!t && t != 4 || n.length != 1) { if (t == -1 || r > 0) e.$keyChain = "" } else e.$keyChain = e.$keyChain.slice(0, -i.length - 1); return { command: o } }, this.getStatusText = function (e, t) { return t.$keyChain || "" } }.call(o.prototype), t.HashHandler = o, t.MultiHashHandler = u }), define("ace/commands/command_manager", ["require", "exports", "module", "ace/lib/oop", "ace/keyboard/hash_handler", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("../keyboard/hash_handler").MultiHashHandler, s = e("../lib/event_emitter").EventEmitter, o = function (e, t) { i.call(this, t, e), this.byName = this.commands, this.setDefaultHandler("exec", function (e) { return e.command.exec(e.editor, e.args || {}) }) }; r.inherits(o, i), function () { r.implement(this, s), this.exec = function (e, t, n) { if (Array.isArray(e)) { for (var r = e.length; r--;)if (this.exec(e[r], t, n)) return !0; return !1 } typeof e == "string" && (e = this.commands[e]); if (!e) return !1; if (t && t.$readOnly && !e.readOnly) return !1; if (this.$checkCommandState != 0 && e.isAvailable && !e.isAvailable(t)) return !1; var i = { editor: t, command: e, args: n }; return i.returnValue = this._emit("exec", i), this._signal("afterExec", i), i.returnValue === !1 ? !1 : !0 }, this.toggleRecording = function (e) { if (this.$inReplay) return; return e && e._emit("changeStatus"), this.recording ? (this.macro.pop(), this.off("exec", this.$addCommandToMacro), this.macro.length || (this.macro = this.oldMacro), this.recording = !1) : (this.$addCommandToMacro || (this.$addCommandToMacro = function (e) { this.macro.push([e.command, e.args]) }.bind(this)), this.oldMacro = this.macro, this.macro = [], this.on("exec", this.$addCommandToMacro), this.recording = !0) }, this.replay = function (e) { if (this.$inReplay || !this.macro) return; if (this.recording) return this.toggleRecording(e); try { this.$inReplay = !0, this.macro.forEach(function (t) { typeof t == "string" ? this.exec(t, e) : this.exec(t[0], e, t[1]) }, this) } finally { this.$inReplay = !1 } }, this.trimMacro = function (e) { return e.map(function (e) { return typeof e[0] != "string" && (e[0] = e[0].name), e[1] || (e = e[0]), e }) } }.call(o.prototype), t.CommandManager = o }), define("ace/commands/default_commands", ["require", "exports", "module", "ace/lib/lang", "ace/config", "ace/range"], function (e, t, n) { "use strict"; function o(e, t) { return { win: e, mac: t } } var r = e("../lib/lang"), i = e("../config"), s = e("../range").Range; t.commands = [{ name: "showSettingsMenu", bindKey: o("Ctrl-,", "Command-,"), exec: function (e) { i.loadModule("ace/ext/settings_menu", function (t) { t.init(e), e.showSettingsMenu() }) }, readOnly: !0 }, { name: "goToNextError", bindKey: o("Alt-E", "F4"), exec: function (e) { i.loadModule("./ext/error_marker", function (t) { t.showErrorMarker(e, 1) }) }, scrollIntoView: "animate", readOnly: !0 }, { name: "goToPreviousError", bindKey: o("Alt-Shift-E", "Shift-F4"), exec: function (e) { i.loadModule("./ext/error_marker", function (t) { t.showErrorMarker(e, -1) }) }, scrollIntoView: "animate", readOnly: !0 }, { name: "selectall", description: "Select all", bindKey: o("Ctrl-A", "Command-A"), exec: function (e) { e.selectAll() }, readOnly: !0 }, { name: "centerselection", description: "Center selection", bindKey: o(null, "Ctrl-L"), exec: function (e) { e.centerSelection() }, readOnly: !0 }, { name: "gotoline", description: "Go to line...", bindKey: o("Ctrl-L", "Command-L"), exec: function (e, t) { typeof t == "number" && !isNaN(t) && e.gotoLine(t), e.prompt({ $type: "gotoLine" }) }, readOnly: !0 }, { name: "fold", bindKey: o("Alt-L|Ctrl-F1", "Command-Alt-L|Command-F1"), exec: function (e) { e.session.toggleFold(!1) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "unfold", bindKey: o("Alt-Shift-L|Ctrl-Shift-F1", "Command-Alt-Shift-L|Command-Shift-F1"), exec: function (e) { e.session.toggleFold(!0) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "toggleFoldWidget", bindKey: o("F2", "F2"), exec: function (e) { e.session.toggleFoldWidget() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "toggleParentFoldWidget", bindKey: o("Alt-F2", "Alt-F2"), exec: function (e) { e.session.toggleFoldWidget(!0) }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "foldall", description: "Fold all", bindKey: o(null, "Ctrl-Command-Option-0"), exec: function (e) { e.session.foldAll() }, scrollIntoView: "center", readOnly: !0 }, { name: "foldOther", description: "Fold other", bindKey: o("Alt-0", "Command-Option-0"), exec: function (e) { e.session.foldAll(), e.session.unfold(e.selection.getAllRanges()) }, scrollIntoView: "center", readOnly: !0 }, { name: "unfoldall", description: "Unfold all", bindKey: o("Alt-Shift-0", "Command-Option-Shift-0"), exec: function (e) { e.session.unfold() }, scrollIntoView: "center", readOnly: !0 }, { name: "findnext", description: "Find next", bindKey: o("Ctrl-K", "Command-G"), exec: function (e) { e.findNext() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "findprevious", description: "Find previous", bindKey: o("Ctrl-Shift-K", "Command-Shift-G"), exec: function (e) { e.findPrevious() }, multiSelectAction: "forEach", scrollIntoView: "center", readOnly: !0 }, { name: "selectOrFindNext", description: "Select or find next", bindKey: o("Alt-K", "Ctrl-G"), exec: function (e) { e.selection.isEmpty() ? e.selection.selectWord() : e.findNext() }, readOnly: !0 }, { name: "selectOrFindPrevious", description: "Select or find previous", bindKey: o("Alt-Shift-K", "Ctrl-Shift-G"), exec: function (e) { e.selection.isEmpty() ? e.selection.selectWord() : e.findPrevious() }, readOnly: !0 }, { name: "find", description: "Find", bindKey: o("Ctrl-F", "Command-F"), exec: function (e) { i.loadModule("ace/ext/searchbox", function (t) { t.Search(e) }) }, readOnly: !0 }, { name: "overwrite", description: "Overwrite", bindKey: "Insert", exec: function (e) { e.toggleOverwrite() }, readOnly: !0 }, { name: "selecttostart", description: "Select to start", bindKey: o("Ctrl-Shift-Home", "Command-Shift-Home|Command-Shift-Up"), exec: function (e) { e.getSelection().selectFileStart() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotostart", description: "Go to start", bindKey: o("Ctrl-Home", "Command-Home|Command-Up"), exec: function (e) { e.navigateFileStart() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectup", description: "Select up", bindKey: o("Shift-Up", "Shift-Up|Ctrl-Shift-P"), exec: function (e) { e.getSelection().selectUp() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "golineup", description: "Go line up", bindKey: o("Up", "Up|Ctrl-P"), exec: function (e, t) { e.navigateUp(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttoend", description: "Select to end", bindKey: o("Ctrl-Shift-End", "Command-Shift-End|Command-Shift-Down"), exec: function (e) { e.getSelection().selectFileEnd() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "gotoend", description: "Go to end", bindKey: o("Ctrl-End", "Command-End|Command-Down"), exec: function (e) { e.navigateFileEnd() }, multiSelectAction: "forEach", readOnly: !0, scrollIntoView: "animate", aceCommandGroup: "fileJump" }, { name: "selectdown", description: "Select down", bindKey: o("Shift-Down", "Shift-Down|Ctrl-Shift-N"), exec: function (e) { e.getSelection().selectDown() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "golinedown", description: "Go line down", bindKey: o("Down", "Down|Ctrl-N"), exec: function (e, t) { e.navigateDown(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectwordleft", description: "Select word left", bindKey: o("Ctrl-Shift-Left", "Option-Shift-Left"), exec: function (e) { e.getSelection().selectWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotowordleft", description: "Go to word left", bindKey: o("Ctrl-Left", "Option-Left"), exec: function (e) { e.navigateWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttolinestart", description: "Select to line start", bindKey: o("Alt-Shift-Left", "Command-Shift-Left|Ctrl-Shift-A"), exec: function (e) { e.getSelection().selectLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotolinestart", description: "Go to line start", bindKey: o("Alt-Left|Home", "Command-Left|Home|Ctrl-A"), exec: function (e) { e.navigateLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectleft", description: "Select left", bindKey: o("Shift-Left", "Shift-Left|Ctrl-Shift-B"), exec: function (e) { e.getSelection().selectLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotoleft", description: "Go to left", bindKey: o("Left", "Left|Ctrl-B"), exec: function (e, t) { e.navigateLeft(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectwordright", description: "Select word right", bindKey: o("Ctrl-Shift-Right", "Option-Shift-Right"), exec: function (e) { e.getSelection().selectWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotowordright", description: "Go to word right", bindKey: o("Ctrl-Right", "Option-Right"), exec: function (e) { e.navigateWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selecttolineend", description: "Select to line end", bindKey: o("Alt-Shift-Right", "Command-Shift-Right|Shift-End|Ctrl-Shift-E"), exec: function (e) { e.getSelection().selectLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotolineend", description: "Go to line end", bindKey: o("Alt-Right|End", "Command-Right|End|Ctrl-E"), exec: function (e) { e.navigateLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectright", description: "Select right", bindKey: o("Shift-Right", "Shift-Right"), exec: function (e) { e.getSelection().selectRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "gotoright", description: "Go to right", bindKey: o("Right", "Right|Ctrl-F"), exec: function (e, t) { e.navigateRight(t.times) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectpagedown", description: "Select page down", bindKey: "Shift-PageDown", exec: function (e) { e.selectPageDown() }, readOnly: !0 }, { name: "pagedown", description: "Page down", bindKey: o(null, "Option-PageDown"), exec: function (e) { e.scrollPageDown() }, readOnly: !0 }, { name: "gotopagedown", description: "Go to page down", bindKey: o("PageDown", "PageDown|Ctrl-V"), exec: function (e) { e.gotoPageDown() }, readOnly: !0 }, { name: "selectpageup", description: "Select page up", bindKey: "Shift-PageUp", exec: function (e) { e.selectPageUp() }, readOnly: !0 }, { name: "pageup", description: "Page up", bindKey: o(null, "Option-PageUp"), exec: function (e) { e.scrollPageUp() }, readOnly: !0 }, { name: "gotopageup", description: "Go to page up", bindKey: "PageUp", exec: function (e) { e.gotoPageUp() }, readOnly: !0 }, { name: "scrollup", description: "Scroll up", bindKey: o("Ctrl-Up", null), exec: function (e) { e.renderer.scrollBy(0, -2 * e.renderer.layerConfig.lineHeight) }, readOnly: !0 }, { name: "scrolldown", description: "Scroll down", bindKey: o("Ctrl-Down", null), exec: function (e) { e.renderer.scrollBy(0, 2 * e.renderer.layerConfig.lineHeight) }, readOnly: !0 }, { name: "selectlinestart", description: "Select line start", bindKey: "Shift-Home", exec: function (e) { e.getSelection().selectLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "selectlineend", description: "Select line end", bindKey: "Shift-End", exec: function (e) { e.getSelection().selectLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "togglerecording", description: "Toggle recording", bindKey: o("Ctrl-Alt-E", "Command-Option-E"), exec: function (e) { e.commands.toggleRecording(e) }, readOnly: !0 }, { name: "replaymacro", description: "Replay macro", bindKey: o("Ctrl-Shift-E", "Command-Shift-E"), exec: function (e) { e.commands.replay(e) }, readOnly: !0 }, { name: "jumptomatching", description: "Jump to matching", bindKey: o("Ctrl-\\|Ctrl-P", "Command-\\"), exec: function (e) { e.jumpToMatching() }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "selecttomatching", description: "Select to matching", bindKey: o("Ctrl-Shift-\\|Ctrl-Shift-P", "Command-Shift-\\"), exec: function (e) { e.jumpToMatching(!0) }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "expandToMatching", description: "Expand to matching", bindKey: o("Ctrl-Shift-M", "Ctrl-Shift-M"), exec: function (e) { e.jumpToMatching(!0, !0) }, multiSelectAction: "forEach", scrollIntoView: "animate", readOnly: !0 }, { name: "passKeysToBrowser", description: "Pass keys to browser", bindKey: o(null, null), exec: function () { }, passEvent: !0, readOnly: !0 }, { name: "copy", description: "Copy", exec: function (e) { }, readOnly: !0 }, { name: "cut", description: "Cut", exec: function (e) { var t = e.$copyWithEmptySelection && e.selection.isEmpty(), n = t ? e.selection.getLineRange() : e.selection.getRange(); e._emit("cut", n), n.isEmpty() || e.session.remove(n), e.clearSelection() }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "paste", description: "Paste", exec: function (e, t) { e.$handlePaste(t) }, scrollIntoView: "cursor" }, { name: "removeline", description: "Remove line", bindKey: o("Ctrl-D", "Command-D"), exec: function (e) { e.removeLines() }, scrollIntoView: "cursor", multiSelectAction: "forEachLine" }, { name: "duplicateSelection", description: "Duplicate selection", bindKey: o("Ctrl-Shift-D", "Command-Shift-D"), exec: function (e) { e.duplicateSelection() }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "sortlines", description: "Sort lines", bindKey: o("Ctrl-Alt-S", "Command-Alt-S"), exec: function (e) { e.sortLines() }, scrollIntoView: "selection", multiSelectAction: "forEachLine" }, { name: "togglecomment", description: "Toggle comment", bindKey: o("Ctrl-/", "Command-/"), exec: function (e) { e.toggleCommentLines() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "toggleBlockComment", description: "Toggle block comment", bindKey: o("Ctrl-Shift-/", "Command-Shift-/"), exec: function (e) { e.toggleBlockComment() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "modifyNumberUp", description: "Modify number up", bindKey: o("Ctrl-Shift-Up", "Alt-Shift-Up"), exec: function (e) { e.modifyNumber(1) }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "modifyNumberDown", description: "Modify number down", bindKey: o("Ctrl-Shift-Down", "Alt-Shift-Down"), exec: function (e) { e.modifyNumber(-1) }, scrollIntoView: "cursor", multiSelectAction: "forEach" }, { name: "replace", description: "Replace", bindKey: o("Ctrl-H", "Command-Option-F"), exec: function (e) { i.loadModule("ace/ext/searchbox", function (t) { t.Search(e, !0) }) } }, { name: "undo", description: "Undo", bindKey: o("Ctrl-Z", "Command-Z"), exec: function (e) { e.undo() } }, { name: "redo", description: "Redo", bindKey: o("Ctrl-Shift-Z|Ctrl-Y", "Command-Shift-Z|Command-Y"), exec: function (e) { e.redo() } }, { name: "copylinesup", description: "Copy lines up", bindKey: o("Alt-Shift-Up", "Command-Option-Up"), exec: function (e) { e.copyLinesUp() }, scrollIntoView: "cursor" }, { name: "movelinesup", description: "Move lines up", bindKey: o("Alt-Up", "Option-Up"), exec: function (e) { e.moveLinesUp() }, scrollIntoView: "cursor" }, { name: "copylinesdown", description: "Copy lines down", bindKey: o("Alt-Shift-Down", "Command-Option-Down"), exec: function (e) { e.copyLinesDown() }, scrollIntoView: "cursor" }, { name: "movelinesdown", description: "Move lines down", bindKey: o("Alt-Down", "Option-Down"), exec: function (e) { e.moveLinesDown() }, scrollIntoView: "cursor" }, { name: "del", description: "Delete", bindKey: o("Delete", "Delete|Ctrl-D|Shift-Delete"), exec: function (e) { e.remove("right") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "backspace", description: "Backspace", bindKey: o("Shift-Backspace|Backspace", "Ctrl-Backspace|Shift-Backspace|Backspace|Ctrl-H"), exec: function (e) { e.remove("left") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "cut_or_delete", description: "Cut or delete", bindKey: o("Shift-Delete", null), exec: function (e) { if (!e.selection.isEmpty()) return !1; e.remove("left") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolinestart", description: "Remove to line start", bindKey: o("Alt-Backspace", "Command-Backspace"), exec: function (e) { e.removeToLineStart() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolineend", description: "Remove to line end", bindKey: o("Alt-Delete", "Ctrl-K|Command-Delete"), exec: function (e) { e.removeToLineEnd() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolinestarthard", description: "Remove to line start hard", bindKey: o("Ctrl-Shift-Backspace", null), exec: function (e) { var t = e.selection.getRange(); t.start.column = 0, e.session.remove(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removetolineendhard", description: "Remove to line end hard", bindKey: o("Ctrl-Shift-Delete", null), exec: function (e) { var t = e.selection.getRange(); t.end.column = Number.MAX_VALUE, e.session.remove(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removewordleft", description: "Remove word left", bindKey: o("Ctrl-Backspace", "Alt-Backspace|Ctrl-Alt-Backspace"), exec: function (e) { e.removeWordLeft() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "removewordright", description: "Remove word right", bindKey: o("Ctrl-Delete", "Alt-Delete"), exec: function (e) { e.removeWordRight() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "outdent", description: "Outdent", bindKey: o("Shift-Tab", "Shift-Tab"), exec: function (e) { e.blockOutdent() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "indent", description: "Indent", bindKey: o("Tab", "Tab"), exec: function (e) { e.indent() }, multiSelectAction: "forEach", scrollIntoView: "selectionPart" }, { name: "blockoutdent", description: "Block outdent", bindKey: o("Ctrl-[", "Ctrl-["), exec: function (e) { e.blockOutdent() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "blockindent", description: "Block indent", bindKey: o("Ctrl-]", "Ctrl-]"), exec: function (e) { e.blockIndent() }, multiSelectAction: "forEachLine", scrollIntoView: "selectionPart" }, { name: "insertstring", description: "Insert string", exec: function (e, t) { e.insert(t) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "inserttext", description: "Insert text", exec: function (e, t) { e.insert(r.stringRepeat(t.text || "", t.times || 1)) }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "splitline", description: "Split line", bindKey: o(null, "Ctrl-O"), exec: function (e) { e.splitLine() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "transposeletters", description: "Transpose letters", bindKey: o("Alt-Shift-X", "Ctrl-T"), exec: function (e) { e.transposeLetters() }, multiSelectAction: function (e) { e.transposeSelections(1) }, scrollIntoView: "cursor" }, { name: "touppercase", description: "To uppercase", bindKey: o("Ctrl-U", "Ctrl-U"), exec: function (e) { e.toUpperCase() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "tolowercase", description: "To lowercase", bindKey: o("Ctrl-Shift-U", "Ctrl-Shift-U"), exec: function (e) { e.toLowerCase() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "autoindent", description: "Auto Indent", bindKey: o(null, null), exec: function (e) { e.autoIndent() }, multiSelectAction: "forEachLine", scrollIntoView: "animate" }, { name: "expandtoline", description: "Expand to line", bindKey: o("Ctrl-Shift-L", "Command-Shift-L"), exec: function (e) { var t = e.selection.getRange(); t.start.column = t.end.column = 0, t.end.row++, e.selection.setRange(t, !1) }, multiSelectAction: "forEach", scrollIntoView: "cursor", readOnly: !0 }, { name: "joinlines", description: "Join lines", bindKey: o(null, null), exec: function (e) { var t = e.selection.isBackwards(), n = t ? e.selection.getSelectionLead() : e.selection.getSelectionAnchor(), i = t ? e.selection.getSelectionAnchor() : e.selection.getSelectionLead(), o = e.session.doc.getLine(n.row).length, u = e.session.doc.getTextRange(e.selection.getRange()), a = u.replace(/\n\s*/, " ").length, f = e.session.doc.getLine(n.row); for (var l = n.row + 1; l <= i.row + 1; l++) { var c = r.stringTrimLeft(r.stringTrimRight(e.session.doc.getLine(l))); c.length !== 0 && (c = " " + c), f += c } i.row + 1 < e.session.doc.getLength() - 1 && (f += e.session.doc.getNewLineCharacter()), e.clearSelection(), e.session.doc.replace(new s(n.row, 0, i.row + 2, 0), f), a > 0 ? (e.selection.moveCursorTo(n.row, n.column), e.selection.selectTo(n.row, n.column + a)) : (o = e.session.doc.getLine(n.row).length > o ? o + 1 : o, e.selection.moveCursorTo(n.row, o)) }, multiSelectAction: "forEach", readOnly: !0 }, { name: "invertSelection", description: "Invert selection", bindKey: o(null, null), exec: function (e) { var t = e.session.doc.getLength() - 1, n = e.session.doc.getLine(t).length, r = e.selection.rangeList.ranges, i = []; r.length < 1 && (r = [e.selection.getRange()]); for (var o = 0; o < r.length; o++)o == r.length - 1 && (r[o].end.row !== t || r[o].end.column !== n) && i.push(new s(r[o].end.row, r[o].end.column, t, n)), o === 0 ? (r[o].start.row !== 0 || r[o].start.column !== 0) && i.push(new s(0, 0, r[o].start.row, r[o].start.column)) : i.push(new s(r[o - 1].end.row, r[o - 1].end.column, r[o].start.row, r[o].start.column)); e.exitMultiSelectMode(), e.clearSelection(); for (var o = 0; o < i.length; o++)e.selection.addRange(i[o], !1) }, readOnly: !0, scrollIntoView: "none" }, { name: "addLineAfter", exec: function (e) { e.selection.clearSelection(), e.navigateLineEnd(), e.insert("\n") }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "addLineBefore", exec: function (e) { e.selection.clearSelection(); var t = e.getCursorPosition(); e.selection.moveTo(t.row - 1, Number.MAX_VALUE), e.insert("\n"), t.row === 0 && e.navigateUp() }, multiSelectAction: "forEach", scrollIntoView: "cursor" }, { name: "openCommandPallete", description: "Open command pallete", bindKey: o("F1", "F1"), exec: function (e) { e.prompt({ $type: "commands" }) }, readOnly: !0 }, { name: "modeSelect", description: "Change language mode...", bindKey: o(null, null), exec: function (e) { e.prompt({ $type: "modes" }) }, readOnly: !0 }] }), define("ace/editor", ["require", "exports", "module", "ace/lib/fixoldbrowsers", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/lib/useragent", "ace/keyboard/textinput", "ace/mouse/mouse_handler", "ace/mouse/fold_handler", "ace/keyboard/keybinding", "ace/edit_session", "ace/search", "ace/range", "ace/lib/event_emitter", "ace/commands/command_manager", "ace/commands/default_commands", "ace/config", "ace/token_iterator", "ace/clipboard"], function (e, t, n) { "use strict"; e("./lib/fixoldbrowsers"); var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./lib/lang"), o = e("./lib/useragent"), u = e("./keyboard/textinput").TextInput, a = e("./mouse/mouse_handler").MouseHandler, f = e("./mouse/fold_handler").FoldHandler, l = e("./keyboard/keybinding").KeyBinding, c = e("./edit_session").EditSession, h = e("./search").Search, p = e("./range").Range, d = e("./lib/event_emitter").EventEmitter, v = e("./commands/command_manager").CommandManager, m = e("./commands/default_commands").commands, g = e("./config"), y = e("./token_iterator").TokenIterator, b = e("./clipboard"), w = function (e, t, n) { this.$toDestroy = []; var r = e.getContainerElement(); this.container = r, this.renderer = e, this.id = "editor" + ++w.$uid, this.commands = new v(o.isMac ? "mac" : "win", m), typeof document == "object" && (this.textInput = new u(e.getTextAreaContainer(), this), this.renderer.textarea = this.textInput.getElement(), this.$mouseHandler = new a(this), new f(this)), this.keyBinding = new l(this), this.$search = (new h).set({ wrap: !0 }), this.$historyTracker = this.$historyTracker.bind(this), this.commands.on("exec", this.$historyTracker), this.$initOperationListeners(), this._$emitInputEvent = s.delayedCall(function () { this._signal("input", {}), this.session && this.session.bgTokenizer && this.session.bgTokenizer.scheduleStart() }.bind(this)), this.on("change", function (e, t) { t._$emitInputEvent.schedule(31) }), this.setSession(t || n && n.session || new c("")), g.resetOptions(this), n && this.setOptions(n), g._signal("editor", this) }; w.$uid = 0, function () { r.implement(this, d), this.$initOperationListeners = function () { this.commands.on("exec", this.startOperation.bind(this), !0), this.commands.on("afterExec", this.endOperation.bind(this), !0), this.$opResetTimer = s.delayedCall(this.endOperation.bind(this, !0)), this.on("change", function () { this.curOp || (this.startOperation(), this.curOp.selectionBefore = this.$lastSel), this.curOp.docChanged = !0 }.bind(this), !0), this.on("changeSelection", function () { this.curOp || (this.startOperation(), this.curOp.selectionBefore = this.$lastSel), this.curOp.selectionChanged = !0 }.bind(this), !0) }, this.curOp = null, this.prevOp = {}, this.startOperation = function (e) { if (this.curOp) { if (!e || this.curOp.command) return; this.prevOp = this.curOp } e || (this.previousCommand = null, e = {}), this.$opResetTimer.schedule(), this.curOp = this.session.curOp = { command: e.command || {}, args: e.args, scrollTop: this.renderer.scrollTop }, this.curOp.selectionBefore = this.selection.toJSON() }, this.endOperation = function (e) { if (this.curOp && this.session) { if (e && e.returnValue === !1 || !this.session) return this.curOp = null; if (e == 1 && this.curOp.command && this.curOp.command.name == "mouse") return; this._signal("beforeEndOperation"); if (!this.curOp) return; var t = this.curOp.command, n = t && t.scrollIntoView; if (n) { switch (n) { case "center-animate": n = "animate"; case "center": this.renderer.scrollCursorIntoView(null, .5); break; case "animate": case "cursor": this.renderer.scrollCursorIntoView(); break; case "selectionPart": var r = this.selection.getRange(), i = this.renderer.layerConfig; (r.start.row >= i.lastRow || r.end.row <= i.firstRow) && this.renderer.scrollSelectionIntoView(this.selection.anchor, this.selection.lead); break; default: }n == "animate" && this.renderer.animateScrolling(this.curOp.scrollTop) } var s = this.selection.toJSON(); this.curOp.selectionAfter = s, this.$lastSel = this.selection.toJSON(), this.session.getUndoManager().addSelection(s), this.prevOp = this.curOp, this.curOp = null } }, this.$mergeableCommands = ["backspace", "del", "insertstring"], this.$historyTracker = function (e) { if (!this.$mergeUndoDeltas) return; var t = this.prevOp, n = this.$mergeableCommands, r = t.command && e.command.name == t.command.name; if (e.command.name == "insertstring") { var i = e.args; this.mergeNextCommand === undefined && (this.mergeNextCommand = !0), r = r && this.mergeNextCommand && (!/\s/.test(i) || /\s/.test(t.args)), this.mergeNextCommand = !0 } else r = r && n.indexOf(e.command.name) !== -1; this.$mergeUndoDeltas != "always" && Date.now() - this.sequenceStartTime > 2e3 && (r = !1), r ? this.session.mergeUndoDeltas = !0 : n.indexOf(e.command.name) !== -1 && (this.sequenceStartTime = Date.now()) }, this.setKeyboardHandler = function (e, t) { if (e && typeof e == "string" && e != "ace") { this.$keybindingId = e; var n = this; g.loadModule(["keybinding", e], function (r) { n.$keybindingId == e && n.keyBinding.setKeyboardHandler(r && r.handler), t && t() }) } else this.$keybindingId = null, this.keyBinding.setKeyboardHandler(e), t && t() }, this.getKeyboardHandler = function () { return this.keyBinding.getKeyboardHandler() }, this.setSession = function (e) { if (this.session == e) return; this.curOp && this.endOperation(), this.curOp = {}; var t = this.session; if (t) { this.session.off("change", this.$onDocumentChange), this.session.off("changeMode", this.$onChangeMode), this.session.off("tokenizerUpdate", this.$onTokenizerUpdate), this.session.off("changeTabSize", this.$onChangeTabSize), this.session.off("changeWrapLimit", this.$onChangeWrapLimit), this.session.off("changeWrapMode", this.$onChangeWrapMode), this.session.off("changeFold", this.$onChangeFold), this.session.off("changeFrontMarker", this.$onChangeFrontMarker), this.session.off("changeBackMarker", this.$onChangeBackMarker), this.session.off("changeBreakpoint", this.$onChangeBreakpoint), this.session.off("changeAnnotation", this.$onChangeAnnotation), this.session.off("changeOverwrite", this.$onCursorChange), this.session.off("changeScrollTop", this.$onScrollTopChange), this.session.off("changeScrollLeft", this.$onScrollLeftChange); var n = this.session.getSelection(); n.off("changeCursor", this.$onCursorChange), n.off("changeSelection", this.$onSelectionChange) } this.session = e, e ? (this.$onDocumentChange = this.onDocumentChange.bind(this), e.on("change", this.$onDocumentChange), this.renderer.setSession(e), this.$onChangeMode = this.onChangeMode.bind(this), e.on("changeMode", this.$onChangeMode), this.$onTokenizerUpdate = this.onTokenizerUpdate.bind(this), e.on("tokenizerUpdate", this.$onTokenizerUpdate), this.$onChangeTabSize = this.renderer.onChangeTabSize.bind(this.renderer), e.on("changeTabSize", this.$onChangeTabSize), this.$onChangeWrapLimit = this.onChangeWrapLimit.bind(this), e.on("changeWrapLimit", this.$onChangeWrapLimit), this.$onChangeWrapMode = this.onChangeWrapMode.bind(this), e.on("changeWrapMode", this.$onChangeWrapMode), this.$onChangeFold = this.onChangeFold.bind(this), e.on("changeFold", this.$onChangeFold), this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this), this.session.on("changeFrontMarker", this.$onChangeFrontMarker), this.$onChangeBackMarker = this.onChangeBackMarker.bind(this), this.session.on("changeBackMarker", this.$onChangeBackMarker), this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this), this.session.on("changeBreakpoint", this.$onChangeBreakpoint), this.$onChangeAnnotation = this.onChangeAnnotation.bind(this), this.session.on("changeAnnotation", this.$onChangeAnnotation), this.$onCursorChange = this.onCursorChange.bind(this), this.session.on("changeOverwrite", this.$onCursorChange), this.$onScrollTopChange = this.onScrollTopChange.bind(this), this.session.on("changeScrollTop", this.$onScrollTopChange), this.$onScrollLeftChange = this.onScrollLeftChange.bind(this), this.session.on("changeScrollLeft", this.$onScrollLeftChange), this.selection = e.getSelection(), this.selection.on("changeCursor", this.$onCursorChange), this.$onSelectionChange = this.onSelectionChange.bind(this), this.selection.on("changeSelection", this.$onSelectionChange), this.onChangeMode(), this.onCursorChange(), this.onScrollTopChange(), this.onScrollLeftChange(), this.onSelectionChange(), this.onChangeFrontMarker(), this.onChangeBackMarker(), this.onChangeBreakpoint(), this.onChangeAnnotation(), this.session.getUseWrapMode() && this.renderer.adjustWrapLimit(), this.renderer.updateFull()) : (this.selection = null, this.renderer.setSession(e)), this._signal("changeSession", { session: e, oldSession: t }), this.curOp = null, t && t._signal("changeEditor", { oldEditor: this }), e && e._signal("changeEditor", { editor: this }), e && e.bgTokenizer && e.bgTokenizer.scheduleStart() }, this.getSession = function () { return this.session }, this.setValue = function (e, t) { return this.session.doc.setValue(e), t ? t == 1 ? this.navigateFileEnd() : t == -1 && this.navigateFileStart() : this.selectAll(), e }, this.getValue = function () { return this.session.getValue() }, this.getSelection = function () { return this.selection }, this.resize = function (e) { this.renderer.onResize(e) }, this.setTheme = function (e, t) { this.renderer.setTheme(e, t) }, this.getTheme = function () { return this.renderer.getTheme() }, this.setStyle = function (e) { this.renderer.setStyle(e) }, this.unsetStyle = function (e) { this.renderer.unsetStyle(e) }, this.getFontSize = function () { return this.getOption("fontSize") || i.computedStyle(this.container).fontSize }, this.setFontSize = function (e) { this.setOption("fontSize", e) }, this.$highlightBrackets = function () { if (this.$highlightPending) return; var e = this; this.$highlightPending = !0, setTimeout(function () { e.$highlightPending = !1; var t = e.session; if (!t || !t.bgTokenizer) return; t.$bracketHighlight && (t.$bracketHighlight.markerIds.forEach(function (e) { t.removeMarker(e) }), t.$bracketHighlight = null); var n = t.getMatchingBracketRanges(e.getCursorPosition()); !n && t.$mode.getMatching && (n = t.$mode.getMatching(e.session)); if (!n) return; var r = "ace_bracket"; Array.isArray(n) ? n.length == 1 && (r = "ace_error_bracket") : n = [n], n.length == 2 && (p.comparePoints(n[0].end, n[1].start) == 0 ? n = [p.fromPoints(n[0].start, n[1].end)] : p.comparePoints(n[0].start, n[1].end) == 0 && (n = [p.fromPoints(n[1].start, n[0].end)])), t.$bracketHighlight = { ranges: n, markerIds: n.map(function (e) { return t.addMarker(e, r, "text") }) } }, 50) }, this.$highlightTags = function () { if (this.$highlightTagPending) return; var e = this; this.$highlightTagPending = !0, setTimeout(function () { e.$highlightTagPending = !1; var t = e.session; if (!t || !t.bgTokenizer) return; var n = e.getCursorPosition(), r = new y(e.session, n.row, n.column), i = r.getCurrentToken(); if (!i || !/\b(?:tag-open|tag-name)/.test(i.type)) { t.removeMarker(t.$tagHighlight), t.$tagHighlight = null; return } if (i.type.indexOf("tag-open") != -1) { i = r.stepForward(); if (!i) return } var s = i.value, o = 0, u = r.stepBackward(); if (u.value == "<") { do u = i, i = r.stepForward(), i && i.value === s && i.type.indexOf("tag-name") !== -1 && (u.value === "<" ? o++ : u.value === "= 0) } else { do i = u, u = r.stepBackward(), i && i.value === s && i.type.indexOf("tag-name") !== -1 && (u.value === "<" ? o++ : u.value === " 1) && (t = !1) } if (e.$highlightLineMarker && !t) e.removeMarker(e.$highlightLineMarker.id), e.$highlightLineMarker = null; else if (!e.$highlightLineMarker && t) { var n = new p(t.row, t.column, t.row, Infinity); n.id = e.addMarker(n, "ace_active-line", "screenLine"), e.$highlightLineMarker = n } else t && (e.$highlightLineMarker.start.row = t.row, e.$highlightLineMarker.end.row = t.row, e.$highlightLineMarker.start.column = t.column, e._signal("changeBackMarker")) }, this.onSelectionChange = function (e) { var t = this.session; t.$selectionMarker && t.removeMarker(t.$selectionMarker), t.$selectionMarker = null; if (!this.selection.isEmpty()) { var n = this.selection.getRange(), r = this.getSelectionStyle(); t.$selectionMarker = t.addMarker(n, "ace_selection", r) } else this.$updateHighlightActiveLine(); var i = this.$highlightSelectedWord && this.$getSelectionHighLightRegexp(); this.session.highlight(i), this._signal("changeSelection") }, this.$getSelectionHighLightRegexp = function () { var e = this.session, t = this.getSelectionRange(); if (t.isEmpty() || t.isMultiLine()) return; var n = t.start.column, r = t.end.column, i = e.getLine(t.start.row), s = i.substring(n, r); if (s.length > 5e3 || !/[\w\d]/.test(s)) return; var o = this.$search.$assembleRegExp({ wholeWord: !0, caseSensitive: !0, needle: s }), u = i.substring(n - 1, r + 1); if (!o.test(u)) return; return o }, this.onChangeFrontMarker = function () { this.renderer.updateFrontMarkers() }, this.onChangeBackMarker = function () { this.renderer.updateBackMarkers() }, this.onChangeBreakpoint = function () { this.renderer.updateBreakpoints() }, this.onChangeAnnotation = function () { this.renderer.setAnnotations(this.session.getAnnotations()) }, this.onChangeMode = function (e) { this.renderer.updateText(), this._emit("changeMode", e) }, this.onChangeWrapLimit = function () { this.renderer.updateFull() }, this.onChangeWrapMode = function () { this.renderer.onResize(!0) }, this.onChangeFold = function () { this.$updateHighlightActiveLine(), this.renderer.updateFull() }, this.getSelectedText = function () { return this.session.getTextRange(this.getSelectionRange()) }, this.getCopyText = function () { var e = this.getSelectedText(), t = this.session.doc.getNewLineCharacter(), n = !1; if (!e && this.$copyWithEmptySelection) { n = !0; var r = this.selection.getAllRanges(); for (var i = 0; i < r.length; i++) { var s = r[i]; if (i && r[i - 1].start.row == s.start.row) continue; e += this.session.getLine(s.start.row) + t } } var o = { text: e }; return this._signal("copy", o), b.lineMode = n ? o.text : "", o.text }, this.onCopy = function () { this.commands.exec("copy", this) }, this.onCut = function () { this.commands.exec("cut", this) }, this.onPaste = function (e, t) { var n = { text: e, event: t }; this.commands.exec("paste", this, n) }, this.$handlePaste = function (e) { typeof e == "string" && (e = { text: e }), this._signal("paste", e); var t = e.text, n = t == b.lineMode, r = this.session; if (!this.inMultiSelectMode || this.inVirtualSelectionMode) n ? r.insert({ row: this.selection.lead.row, column: 0 }, t) : this.insert(t); else if (n) this.selection.rangeList.ranges.forEach(function (e) { r.insert({ row: e.start.row, column: 0 }, t) }); else { var i = t.split(/\r\n|\r|\n/), s = this.selection.rangeList.ranges, o = i.length == 2 && (!i[0] || !i[1]); if (i.length != s.length || o) return this.commands.exec("insertstring", this, t); for (var u = s.length; u--;) { var a = s[u]; a.isEmpty() || r.remove(a), r.insert(a.start, i[u]) } } }, this.execCommand = function (e, t) { return this.commands.exec(e, this, t) }, this.insert = function (e, t) { var n = this.session, r = n.getMode(), i = this.getCursorPosition(); if (this.getBehavioursEnabled() && !t) { var s = r.transformAction(n.getState(i.row), "insertion", this, n, e); s && (e !== s.text && (this.inVirtualSelectionMode || (this.session.mergeUndoDeltas = !1, this.mergeNextCommand = !1)), e = s.text) } e == " " && (e = this.session.getTabString()); if (!this.selection.isEmpty()) { var o = this.getSelectionRange(); i = this.session.remove(o), this.clearSelection() } else if (this.session.getOverwrite() && e.indexOf("\n") == -1) { var o = new p.fromPoints(i, i); o.end.column += e.length, this.session.remove(o) } if (e == "\n" || e == "\r\n") { var u = n.getLine(i.row); if (i.column > u.search(/\S|$/)) { var a = u.substr(i.column).search(/\S|$/); n.doc.removeInLine(i.row, i.column, i.column + a) } } this.clearSelection(); var f = i.column, l = n.getState(i.row), u = n.getLine(i.row), c = r.checkOutdent(l, u, e); n.insert(i, e), s && s.selection && (s.selection.length == 2 ? this.selection.setSelectionRange(new p(i.row, f + s.selection[0], i.row, f + s.selection[1])) : this.selection.setSelectionRange(new p(i.row + s.selection[0], s.selection[1], i.row + s.selection[2], s.selection[3]))); if (this.$enableAutoIndent) { if (n.getDocument().isNewLine(e)) { var h = r.getNextLineIndent(l, u.slice(0, i.column), n.getTabString()); n.insert({ row: i.row + 1, column: 0 }, h) } c && r.autoOutdent(l, n, i.row) } }, this.autoIndent = function () { var e = this.session, t = e.getMode(), n, r; if (this.selection.isEmpty()) n = 0, r = e.doc.getLength() - 1; else { var i = this.getSelectionRange(); n = i.start.row, r = i.end.row } var s = "", o = "", u = "", a, f, l, c = e.getTabString(); for (var h = n; h <= r; h++)h > 0 && (s = e.getState(h - 1), o = e.getLine(h - 1), u = t.getNextLineIndent(s, o, c)), a = e.getLine(h), f = t.$getIndent(a), u !== f && (f.length > 0 && (l = new p(h, 0, h, f.length), e.remove(l)), u.length > 0 && e.insert({ row: h, column: 0 }, u)), t.autoOutdent(s, e, h) }, this.onTextInput = function (e, t) { if (!t) return this.keyBinding.onTextInput(e); this.startOperation({ command: { name: "insertstring" } }); var n = this.applyComposition.bind(this, e, t); this.selection.rangeCount ? this.forEachSelection(n) : n(), this.endOperation() }, this.applyComposition = function (e, t) { if (t.extendLeft || t.extendRight) { var n = this.selection.getRange(); n.start.column -= t.extendLeft, n.end.column += t.extendRight, n.start.column < 0 && (n.start.row--, n.start.column += this.session.getLine(n.start.row).length + 1), this.selection.setRange(n), !e && !n.isEmpty() && this.remove() } (e || !this.selection.isEmpty()) && this.insert(e, !0); if (t.restoreStart || t.restoreEnd) { var n = this.selection.getRange(); n.start.column -= t.restoreStart, n.end.column -= t.restoreEnd, this.selection.setRange(n) } }, this.onCommandKey = function (e, t, n) { return this.keyBinding.onCommandKey(e, t, n) }, this.setOverwrite = function (e) { this.session.setOverwrite(e) }, this.getOverwrite = function () { return this.session.getOverwrite() }, this.toggleOverwrite = function () { this.session.toggleOverwrite() }, this.setScrollSpeed = function (e) { this.setOption("scrollSpeed", e) }, this.getScrollSpeed = function () { return this.getOption("scrollSpeed") }, this.setDragDelay = function (e) { this.setOption("dragDelay", e) }, this.getDragDelay = function () { return this.getOption("dragDelay") }, this.setSelectionStyle = function (e) { this.setOption("selectionStyle", e) }, this.getSelectionStyle = function () { return this.getOption("selectionStyle") }, this.setHighlightActiveLine = function (e) { this.setOption("highlightActiveLine", e) }, this.getHighlightActiveLine = function () { return this.getOption("highlightActiveLine") }, this.setHighlightGutterLine = function (e) { this.setOption("highlightGutterLine", e) }, this.getHighlightGutterLine = function () { return this.getOption("highlightGutterLine") }, this.setHighlightSelectedWord = function (e) { this.setOption("highlightSelectedWord", e) }, this.getHighlightSelectedWord = function () { return this.$highlightSelectedWord }, this.setAnimatedScroll = function (e) { this.renderer.setAnimatedScroll(e) }, this.getAnimatedScroll = function () { return this.renderer.getAnimatedScroll() }, this.setShowInvisibles = function (e) { this.renderer.setShowInvisibles(e) }, this.getShowInvisibles = function () { return this.renderer.getShowInvisibles() }, this.setDisplayIndentGuides = function (e) { this.renderer.setDisplayIndentGuides(e) }, this.getDisplayIndentGuides = function () { return this.renderer.getDisplayIndentGuides() }, this.setShowPrintMargin = function (e) { this.renderer.setShowPrintMargin(e) }, this.getShowPrintMargin = function () { return this.renderer.getShowPrintMargin() }, this.setPrintMarginColumn = function (e) { this.renderer.setPrintMarginColumn(e) }, this.getPrintMarginColumn = function () { return this.renderer.getPrintMarginColumn() }, this.setReadOnly = function (e) { this.setOption("readOnly", e) }, this.getReadOnly = function () { return this.getOption("readOnly") }, this.setBehavioursEnabled = function (e) { this.setOption("behavioursEnabled", e) }, this.getBehavioursEnabled = function () { return this.getOption("behavioursEnabled") }, this.setWrapBehavioursEnabled = function (e) { this.setOption("wrapBehavioursEnabled", e) }, this.getWrapBehavioursEnabled = function () { return this.getOption("wrapBehavioursEnabled") }, this.setShowFoldWidgets = function (e) { this.setOption("showFoldWidgets", e) }, this.getShowFoldWidgets = function () { return this.getOption("showFoldWidgets") }, this.setFadeFoldWidgets = function (e) { this.setOption("fadeFoldWidgets", e) }, this.getFadeFoldWidgets = function () { return this.getOption("fadeFoldWidgets") }, this.remove = function (e) { this.selection.isEmpty() && (e == "left" ? this.selection.selectLeft() : this.selection.selectRight()); var t = this.getSelectionRange(); if (this.getBehavioursEnabled()) { var n = this.session, r = n.getState(t.start.row), i = n.getMode().transformAction(r, "deletion", this, n, t); if (t.end.column === 0) { var s = n.getTextRange(t); if (s[s.length - 1] == "\n") { var o = n.getLine(t.end.row); /^\s+$/.test(o) && (t.end.column = o.length) } } i && (t = i) } this.session.remove(t), this.clearSelection() }, this.removeWordRight = function () { this.selection.isEmpty() && this.selection.selectWordRight(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeWordLeft = function () { this.selection.isEmpty() && this.selection.selectWordLeft(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeToLineStart = function () { this.selection.isEmpty() && this.selection.selectLineStart(), this.selection.isEmpty() && this.selection.selectLeft(), this.session.remove(this.getSelectionRange()), this.clearSelection() }, this.removeToLineEnd = function () { this.selection.isEmpty() && this.selection.selectLineEnd(); var e = this.getSelectionRange(); e.start.column == e.end.column && e.start.row == e.end.row && (e.end.column = 0, e.end.row++), this.session.remove(e), this.clearSelection() }, this.splitLine = function () { this.selection.isEmpty() || (this.session.remove(this.getSelectionRange()), this.clearSelection()); var e = this.getCursorPosition(); this.insert("\n"), this.moveCursorToPosition(e) }, this.transposeLetters = function () { if (!this.selection.isEmpty()) return; var e = this.getCursorPosition(), t = e.column; if (t === 0) return; var n = this.session.getLine(e.row), r, i; t < n.length ? (r = n.charAt(t) + n.charAt(t - 1), i = new p(e.row, t - 1, e.row, t + 1)) : (r = n.charAt(t - 1) + n.charAt(t - 2), i = new p(e.row, t - 2, e.row, t)), this.session.replace(i, r), this.session.selection.moveToPosition(i.end) }, this.toLowerCase = function () { var e = this.getSelectionRange(); this.selection.isEmpty() && this.selection.selectWord(); var t = this.getSelectionRange(), n = this.session.getTextRange(t); this.session.replace(t, n.toLowerCase()), this.selection.setSelectionRange(e) }, this.toUpperCase = function () { var e = this.getSelectionRange(); this.selection.isEmpty() && this.selection.selectWord(); var t = this.getSelectionRange(), n = this.session.getTextRange(t); this.session.replace(t, n.toUpperCase()), this.selection.setSelectionRange(e) }, this.indent = function () { var e = this.session, t = this.getSelectionRange(); if (t.start.row < t.end.row) { var n = this.$getSelectedRows(); e.indentRows(n.first, n.last, " "); return } if (t.start.column < t.end.column) { var r = e.getTextRange(t); if (!/^\s+$/.test(r)) { var n = this.$getSelectedRows(); e.indentRows(n.first, n.last, " "); return } } var i = e.getLine(t.start.row), o = t.start, u = e.getTabSize(), a = e.documentToScreenColumn(o.row, o.column); if (this.session.getUseSoftTabs()) var f = u - a % u, l = s.stringRepeat(" ", f); else { var f = a % u; while (i[t.start.column - 1] == " " && f) t.start.column--, f--; this.selection.setSelectionRange(t), l = " " } return this.insert(l) }, this.blockIndent = function () { var e = this.$getSelectedRows(); this.session.indentRows(e.first, e.last, " ") }, this.blockOutdent = function () { var e = this.session.getSelection(); this.session.outdentRows(e.getRange()) }, this.sortLines = function () { var e = this.$getSelectedRows(), t = this.session, n = []; for (var r = e.first; r <= e.last; r++)n.push(t.getLine(r)); n.sort(function (e, t) { return e.toLowerCase() < t.toLowerCase() ? -1 : e.toLowerCase() > t.toLowerCase() ? 1 : 0 }); var i = new p(0, 0, 0, 0); for (var r = e.first; r <= e.last; r++) { var s = t.getLine(r); i.start.row = r, i.end.row = r, i.end.column = s.length, t.replace(i, n[r - e.first]) } }, this.toggleCommentLines = function () { var e = this.session.getState(this.getCursorPosition().row), t = this.$getSelectedRows(); this.session.getMode().toggleCommentLines(e, this.session, t.first, t.last) }, this.toggleBlockComment = function () { var e = this.getCursorPosition(), t = this.session.getState(e.row), n = this.getSelectionRange(); this.session.getMode().toggleBlockComment(t, this.session, n, e) }, this.getNumberAt = function (e, t) { var n = /[\-]?[0-9]+(?:\.[0-9]+)?/g; n.lastIndex = 0; var r = this.session.getLine(e); while (n.lastIndex < t) { var i = n.exec(r); if (i.index <= t && i.index + i[0].length >= t) { var s = { value: i[0], start: i.index, end: i.index + i[0].length }; return s } } return null }, this.modifyNumber = function (e) { var t = this.selection.getCursor().row, n = this.selection.getCursor().column, r = new p(t, n - 1, t, n), i = this.session.getTextRange(r); if (!isNaN(parseFloat(i)) && isFinite(i)) { var s = this.getNumberAt(t, n); if (s) { var o = s.value.indexOf(".") >= 0 ? s.start + s.value.indexOf(".") + 1 : s.end, u = s.start + s.value.length - o, a = parseFloat(s.value); a *= Math.pow(10, u), o !== s.end && n < o ? e *= Math.pow(10, s.end - n - 1) : e *= Math.pow(10, s.end - n), a += e, a /= Math.pow(10, u); var f = a.toFixed(u), l = new p(t, s.start, t, s.end); this.session.replace(l, f), this.moveCursorTo(t, Math.max(s.start + 1, n + f.length - s.value.length)) } } else this.toggleWord() }, this.$toggleWordPairs = [["first", "last"], ["true", "false"], ["yes", "no"], ["width", "height"], ["top", "bottom"], ["right", "left"], ["on", "off"], ["x", "y"], ["get", "set"], ["max", "min"], ["horizontal", "vertical"], ["show", "hide"], ["add", "remove"], ["up", "down"], ["before", "after"], ["even", "odd"], ["in", "out"], ["inside", "outside"], ["next", "previous"], ["increase", "decrease"], ["attach", "detach"], ["&&", "||"], ["==", "!="]], this.toggleWord = function () { var e = this.selection.getCursor().row, t = this.selection.getCursor().column; this.selection.selectWord(); var n = this.getSelectedText(), r = this.selection.getWordRange().start.column, i = n.replace(/([a-z]+|[A-Z]+)(?=[A-Z_]|$)/g, "$1 ").split(/\s/), o = t - r - 1; o < 0 && (o = 0); var u = 0, a = 0, f = this; n.match(/[A-Za-z0-9_]+/) && i.forEach(function (t, i) { a = u + t.length, o >= u && o <= a && (n = t, f.selection.clearSelection(), f.moveCursorTo(e, u + r), f.selection.selectTo(e, a + r)), u = a }); var l = this.$toggleWordPairs, c; for (var h = 0; h < l.length; h++) { var p = l[h]; for (var d = 0; d <= 1; d++) { var v = +!d, m = n.match(new RegExp("^\\s?_?(" + s.escapeRegExp(p[d]) + ")\\s?$", "i")); if (m) { var g = n.match(new RegExp("([_]|^|\\s)(" + s.escapeRegExp(m[1]) + ")($|\\s)", "g")); g && (c = n.replace(new RegExp(s.escapeRegExp(p[d]), "i"), function (e) { var t = p[v]; return e.toUpperCase() == e ? t = t.toUpperCase() : e.charAt(0).toUpperCase() == e.charAt(0) && (t = t.substr(0, 0) + p[v].charAt(0).toUpperCase() + t.substr(1)), t }), this.insert(c), c = "") } } } }, this.removeLines = function () { var e = this.$getSelectedRows(); this.session.removeFullLines(e.first, e.last), this.clearSelection() }, this.duplicateSelection = function () { var e = this.selection, t = this.session, n = e.getRange(), r = e.isBackwards(); if (n.isEmpty()) { var i = n.start.row; t.duplicateLines(i, i) } else { var s = r ? n.start : n.end, o = t.insert(s, t.getTextRange(n), !1); n.start = s, n.end = o, e.setSelectionRange(n, r) } }, this.moveLinesDown = function () { this.$moveLines(1, !1) }, this.moveLinesUp = function () { this.$moveLines(-1, !1) }, this.moveText = function (e, t, n) { return this.session.moveText(e, t, n) }, this.copyLinesUp = function () { this.$moveLines(-1, !0) }, this.copyLinesDown = function () { this.$moveLines(1, !0) }, this.$moveLines = function (e, t) { var n, r, i = this.selection; if (!i.inMultiSelectMode || this.inVirtualSelectionMode) { var s = i.toOrientedRange(); n = this.$getSelectedRows(s), r = this.session.$moveLines(n.first, n.last, t ? 0 : e), t && e == -1 && (r = 0), s.moveBy(r, 0), i.fromOrientedRange(s) } else { var o = i.rangeList.ranges; i.rangeList.detach(this.session), this.inVirtualSelectionMode = !0; var u = 0, a = 0, f = o.length; for (var l = 0; l < f; l++) { var c = l; o[l].moveBy(u, 0), n = this.$getSelectedRows(o[l]); var h = n.first, p = n.last; while (++l < f) { a && o[l].moveBy(a, 0); var d = this.$getSelectedRows(o[l]); if (t && d.first != p) break; if (!t && d.first > p + 1) break; p = d.last } l--, u = this.session.$moveLines(h, p, t ? 0 : e), t && e == -1 && (c = l + 1); while (c <= l) o[c].moveBy(u, 0), c++; t || (u = 0), a += u } i.fromOrientedRange(i.ranges[0]), i.rangeList.attach(this.session), this.inVirtualSelectionMode = !1 } }, this.$getSelectedRows = function (e) { return e = (e || this.getSelectionRange()).collapseRows(), { first: this.session.getRowFoldStart(e.start.row), last: this.session.getRowFoldEnd(e.end.row) } }, this.onCompositionStart = function (e) { this.renderer.showComposition(e) }, this.onCompositionUpdate = function (e) { this.renderer.setCompositionText(e) }, this.onCompositionEnd = function () { this.renderer.hideComposition() }, this.getFirstVisibleRow = function () { return this.renderer.getFirstVisibleRow() }, this.getLastVisibleRow = function () { return this.renderer.getLastVisibleRow() }, this.isRowVisible = function (e) { return e >= this.getFirstVisibleRow() && e <= this.getLastVisibleRow() }, this.isRowFullyVisible = function (e) { return e >= this.renderer.getFirstFullyVisibleRow() && e <= this.renderer.getLastFullyVisibleRow() }, this.$getVisibleRowCount = function () { return this.renderer.getScrollBottomRow() - this.renderer.getScrollTopRow() + 1 }, this.$moveByPage = function (e, t) { var n = this.renderer, r = this.renderer.layerConfig, i = e * Math.floor(r.height / r.lineHeight); t === !0 ? this.selection.$moveSelection(function () { this.moveCursorBy(i, 0) }) : t === !1 && (this.selection.moveCursorBy(i, 0), this.selection.clearSelection()); var s = n.scrollTop; n.scrollBy(0, i * r.lineHeight), t != null && n.scrollCursorIntoView(null, .5), n.animateScrolling(s) }, this.selectPageDown = function () { this.$moveByPage(1, !0) }, this.selectPageUp = function () { this.$moveByPage(-1, !0) }, this.gotoPageDown = function () { this.$moveByPage(1, !1) }, this.gotoPageUp = function () { this.$moveByPage(-1, !1) }, this.scrollPageDown = function () { this.$moveByPage(1) }, this.scrollPageUp = function () { this.$moveByPage(-1) }, this.scrollToRow = function (e) { this.renderer.scrollToRow(e) }, this.scrollToLine = function (e, t, n, r) { this.renderer.scrollToLine(e, t, n, r) }, this.centerSelection = function () { var e = this.getSelectionRange(), t = { row: Math.floor(e.start.row + (e.end.row - e.start.row) / 2), column: Math.floor(e.start.column + (e.end.column - e.start.column) / 2) }; this.renderer.alignCursor(t, .5) }, this.getCursorPosition = function () { return this.selection.getCursor() }, this.getCursorPositionScreen = function () { return this.session.documentToScreenPosition(this.getCursorPosition()) }, this.getSelectionRange = function () { return this.selection.getRange() }, this.selectAll = function () { this.selection.selectAll() }, this.clearSelection = function () { this.selection.clearSelection() }, this.moveCursorTo = function (e, t) { this.selection.moveCursorTo(e, t) }, this.moveCursorToPosition = function (e) { this.selection.moveCursorToPosition(e) }, this.jumpToMatching = function (e, t) { var n = this.getCursorPosition(), r = new y(this.session, n.row, n.column), i = r.getCurrentToken(), s = i || r.stepForward(); if (!s) return; var o, u = !1, a = {}, f = n.column - s.start, l, c = { ")": "(", "(": "(", "]": "[", "[": "[", "{": "{", "}": "{" }; do { if (s.value.match(/[{}()\[\]]/g)) for (; f < s.value.length && !u; f++) { if (!c[s.value[f]]) continue; l = c[s.value[f]] + "." + s.type.replace("rparen", "lparen"), isNaN(a[l]) && (a[l] = 0); switch (s.value[f]) { case "(": case "[": case "{": a[l]++; break; case ")": case "]": case "}": a[l]--, a[l] === -1 && (o = "bracket", u = !0) } } else s.type.indexOf("tag-name") !== -1 && (isNaN(a[s.value]) && (a[s.value] = 0), i.value === "<" ? a[s.value]++ : i.value === "= 0; --s)this.$tryReplace(n[s], e) && r++; return this.selection.setSelectionRange(i), r }, this.$tryReplace = function (e, t) { var n = this.session.getTextRange(e); return t = this.$search.replace(n, t), t !== null ? (e.end = this.session.replace(e, t), e) : null }, this.getLastSearchOptions = function () { return this.$search.getOptions() }, this.find = function (e, t, n) { t || (t = {}), typeof e == "string" || e instanceof RegExp ? t.needle = e : typeof e == "object" && r.mixin(t, e); var i = this.selection.getRange(); t.needle == null && (e = this.session.getTextRange(i) || this.$search.$options.needle, e || (i = this.session.getWordRange(i.start.row, i.start.column), e = this.session.getTextRange(i)), this.$search.set({ needle: e })), this.$search.set(t), t.start || this.$search.set({ start: i }); var s = this.$search.find(this.session); if (t.preventScroll) return s; if (s) return this.revealRange(s, n), s; t.backwards ? i.start = i.end : i.end = i.start, this.selection.setRange(i) }, this.findNext = function (e, t) { this.find({ skipCurrent: !0, backwards: !1 }, e, t) }, this.findPrevious = function (e, t) { this.find(e, { skipCurrent: !0, backwards: !0 }, t) }, this.revealRange = function (e, t) { this.session.unfold(e), this.selection.setSelectionRange(e); var n = this.renderer.scrollTop; this.renderer.scrollSelectionIntoView(e.start, e.end, .5), t !== !1 && this.renderer.animateScrolling(n) }, this.undo = function () { this.session.getUndoManager().undo(this.session), this.renderer.scrollCursorIntoView(null, .5) }, this.redo = function () { this.session.getUndoManager().redo(this.session), this.renderer.scrollCursorIntoView(null, .5) }, this.destroy = function () { this.$toDestroy && (this.$toDestroy.forEach(function (e) { e.destroy() }), this.$toDestroy = null), this.renderer.destroy(), this._signal("destroy", this), this.session && this.session.destroy(), this._$emitInputEvent && this._$emitInputEvent.cancel(), this.removeAllListeners() }, this.setAutoScrollEditorIntoView = function (e) { if (!e) return; var t, n = this, r = !1; this.$scrollAnchor || (this.$scrollAnchor = document.createElement("div")); var i = this.$scrollAnchor; i.style.cssText = "position:absolute", this.container.insertBefore(i, this.container.firstChild); var s = this.on("changeSelection", function () { r = !0 }), o = this.renderer.on("beforeRender", function () { r && (t = n.renderer.container.getBoundingClientRect()) }), u = this.renderer.on("afterRender", function () { if (r && t && (n.isFocused() || n.searchBox && n.searchBox.isFocused())) { var e = n.renderer, s = e.$cursorLayer.$pixelPos, o = e.layerConfig, u = s.top - o.offset; s.top >= 0 && u + t.top < 0 ? r = !0 : s.top < o.height && s.top + t.top + o.lineHeight > window.innerHeight ? r = !1 : r = null, r != null && (i.style.top = u + "px", i.style.left = s.left + "px", i.style.height = o.lineHeight + "px", i.scrollIntoView(r)), r = t = null } }); this.setAutoScrollEditorIntoView = function (e) { if (e) return; delete this.setAutoScrollEditorIntoView, this.off("changeSelection", s), this.renderer.off("afterRender", u), this.renderer.off("beforeRender", o) } }, this.$resetCursorStyle = function () { var e = this.$cursorStyle || "ace", t = this.renderer.$cursorLayer; if (!t) return; t.setSmoothBlinking(/smooth/.test(e)), t.isBlinking = !this.$readOnly && e != "wide", i.setCssClass(t.element, "ace_slim-cursors", /slim/.test(e)) }, this.prompt = function (e, t, n) { var r = this; g.loadModule("./ext/prompt", function (i) { i.prompt(r, e, t, n) }) } }.call(w.prototype), g.defineOptions(w.prototype, "editor", { selectionStyle: { set: function (e) { this.onSelectionChange(), this._signal("changeSelectionStyle", { data: e }) }, initialValue: "line" }, highlightActiveLine: { set: function () { this.$updateHighlightActiveLine() }, initialValue: !0 }, highlightSelectedWord: { set: function (e) { this.$onSelectionChange() }, initialValue: !0 }, readOnly: { set: function (e) { this.textInput.setReadOnly(e), this.$resetCursorStyle() }, initialValue: !1 }, copyWithEmptySelection: { set: function (e) { this.textInput.setCopyWithEmptySelection(e) }, initialValue: !1 }, cursorStyle: { set: function (e) { this.$resetCursorStyle() }, values: ["ace", "slim", "smooth", "wide"], initialValue: "ace" }, mergeUndoDeltas: { values: [!1, !0, "always"], initialValue: !0 }, behavioursEnabled: { initialValue: !0 }, wrapBehavioursEnabled: { initialValue: !0 }, enableAutoIndent: { initialValue: !0 }, autoScrollEditorIntoView: { set: function (e) { this.setAutoScrollEditorIntoView(e) } }, keyboardHandler: { set: function (e) { this.setKeyboardHandler(e) }, get: function () { return this.$keybindingId }, handlesSet: !0 }, value: { set: function (e) { this.session.setValue(e) }, get: function () { return this.getValue() }, handlesSet: !0, hidden: !0 }, session: { set: function (e) { this.setSession(e) }, get: function () { return this.session }, handlesSet: !0, hidden: !0 }, showLineNumbers: { set: function (e) { this.renderer.$gutterLayer.setShowLineNumbers(e), this.renderer.$loop.schedule(this.renderer.CHANGE_GUTTER), e && this.$relativeLineNumbers ? E.attach(this) : E.detach(this) }, initialValue: !0 }, relativeLineNumbers: { set: function (e) { this.$showLineNumbers && e ? E.attach(this) : E.detach(this) } }, placeholder: { set: function (e) { this.$updatePlaceholder || (this.$updatePlaceholder = function () { var e = this.session && (this.renderer.$composition || this.getValue()); if (e && this.renderer.placeholderNode) this.renderer.off("afterRender", this.$updatePlaceholder), i.removeCssClass(this.container, "ace_hasPlaceholder"), this.renderer.placeholderNode.remove(), this.renderer.placeholderNode = null; else if (!e && !this.renderer.placeholderNode) { this.renderer.on("afterRender", this.$updatePlaceholder), i.addCssClass(this.container, "ace_hasPlaceholder"); var t = i.createElement("div"); t.className = "ace_placeholder", t.textContent = this.$placeholder || "", this.renderer.placeholderNode = t, this.renderer.content.appendChild(this.renderer.placeholderNode) } else !e && this.renderer.placeholderNode && (this.renderer.placeholderNode.textContent = this.$placeholder || "") }.bind(this), this.on("input", this.$updatePlaceholder)), this.$updatePlaceholder() } }, hScrollBarAlwaysVisible: "renderer", vScrollBarAlwaysVisible: "renderer", highlightGutterLine: "renderer", animatedScroll: "renderer", showInvisibles: "renderer", showPrintMargin: "renderer", printMarginColumn: "renderer", printMargin: "renderer", fadeFoldWidgets: "renderer", showFoldWidgets: "renderer", displayIndentGuides: "renderer", showGutter: "renderer", fontSize: "renderer", fontFamily: "renderer", maxLines: "renderer", minLines: "renderer", scrollPastEnd: "renderer", fixedWidthGutter: "renderer", theme: "renderer", hasCssTransforms: "renderer", maxPixelHeight: "renderer", useTextareaForIME: "renderer", scrollSpeed: "$mouseHandler", dragDelay: "$mouseHandler", dragEnabled: "$mouseHandler", focusTimeout: "$mouseHandler", tooltipFollowsMouse: "$mouseHandler", firstLineNumber: "session", overwrite: "session", newLineMode: "session", useWorker: "session", useSoftTabs: "session", navigateWithinSoftTabs: "session", tabSize: "session", wrap: "session", indentedSoftWrap: "session", foldStyle: "session", mode: "session" }); var E = { getText: function (e, t) { return (Math.abs(e.selection.lead.row - t) || t + 1 + (t < 9 ? "\u00b7" : "")) + "" }, getWidth: function (e, t, n) { return Math.max(t.toString().length, (n.lastRow + 1).toString().length, 2) * n.characterWidth }, update: function (e, t) { t.renderer.$loop.schedule(t.renderer.CHANGE_GUTTER) }, attach: function (e) { e.renderer.$gutterLayer.$renderer = this, e.on("changeSelection", this.update), this.update(null, e) }, detach: function (e) { e.renderer.$gutterLayer.$renderer == this && (e.renderer.$gutterLayer.$renderer = null), e.off("changeSelection", this.update), this.update(null, e) } }; t.Editor = w }), define("ace/undomanager", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; function i(e, t) { for (var n = t; n--;) { var r = e[n]; if (r && !r[0].ignore) { while (n < t - 1) { var i = d(e[n], e[n + 1]); e[n] = i[0], e[n + 1] = i[1], n++ } return !0 } } } function a(e) { var t = e.action == "insert", n = e.start, r = e.end, i = (r.row - n.row) * (t ? 1 : -1), s = (r.column - n.column) * (t ? 1 : -1); t && (r = n); for (var o in this.marks) { var a = this.marks[o], f = u(a, n); if (f < 0) continue; if (f === 0 && t) { if (a.bias != 1) { a.bias == -1; continue } f = 1 } var l = t ? f : u(a, r); if (l > 0) { a.row += i, a.column += a.row == r.row ? s : 0; continue } !t && l <= 0 && (a.row = n.row, a.column = n.column, l === 0 && (a.bias = 1)) } } function f(e) { return { row: e.row, column: e.column } } function l(e) { return { start: f(e.start), end: f(e.end), action: e.action, lines: e.lines.slice() } } function c(e) { e = e || this; if (Array.isArray(e)) return e.map(c).join("\n"); var t = ""; e.action ? (t = e.action == "insert" ? "+" : "-", t += "[" + e.lines + "]") : e.value && (Array.isArray(e.value) ? t = e.value.map(h).join("\n") : t = h(e.value)), e.start && (t += h(e)); if (e.id || e.rev) t += " (" + (e.id || e.rev) + ")"; return t } function h(e) { return e.start.row + ":" + e.start.column + "=>" + e.end.row + ":" + e.end.column } function p(e, t) { var n = e.action == "insert", r = t.action == "insert"; if (n && r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.start, e.start) <= 0)) return null; m(e, t, 1) } else if (n && !r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.end, e.start) <= 0)) return null; m(e, t, -1) } else if (!n && r) if (o(t.start, e.start) >= 0) m(t, e, 1); else { if (!(o(t.start, e.start) <= 0)) return null; m(e, t, 1) } else if (!n && !r) if (o(t.start, e.start) >= 0) m(t, e, 1); else { if (!(o(t.end, e.start) <= 0)) return null; m(e, t, -1) } return [t, e] } function d(e, t) { for (var n = e.length; n--;)for (var r = 0; r < t.length; r++)if (!p(e[n], t[r])) { while (n < e.length) { while (r--) p(t[r], e[n]); r = t.length, n++ } return [e, t] } return e.selectionBefore = t.selectionBefore = e.selectionAfter = t.selectionAfter = null, [t, e] } function v(e, t) { var n = e.action == "insert", r = t.action == "insert"; if (n && r) o(e.start, t.start) < 0 ? m(t, e, 1) : m(e, t, 1); else if (n && !r) o(e.start, t.end) >= 0 ? m(e, t, -1) : o(e.start, t.start) <= 0 ? m(t, e, 1) : (m(e, s.fromPoints(t.start, e.start), -1), m(t, e, 1)); else if (!n && r) o(t.start, e.end) >= 0 ? m(t, e, -1) : o(t.start, e.start) <= 0 ? m(e, t, 1) : (m(t, s.fromPoints(e.start, t.start), -1), m(e, t, 1)); else if (!n && !r) if (o(t.start, e.end) >= 0) m(t, e, -1); else { if (!(o(t.end, e.start) <= 0)) { var i, u; return o(e.start, t.start) < 0 && (i = e, e = y(e, t.start)), o(e.end, t.end) > 0 && (u = y(e, t.end)), g(t.end, e.start, e.end, -1), u && !i && (e.lines = u.lines, e.start = u.start, e.end = u.end, u = e), [t, i, u].filter(Boolean) } m(e, t, -1) } return [t, e] } function m(e, t, n) { g(e.start, t.start, t.end, n), g(e.end, t.start, t.end, n) } function g(e, t, n, r) { e.row == (r == 1 ? t : n).row && (e.column += r * (n.column - t.column)), e.row += r * (n.row - t.row) } function y(e, t) { var n = e.lines, r = e.end; e.end = f(t); var i = e.end.row - e.start.row, s = n.splice(i, n.length), o = i ? t.column : t.column - e.start.column; n.push(s[0].substring(0, o)), s[0] = s[0].substr(o); var u = { start: f(t), end: r, lines: s, action: e.action }; return u } function b(e, t) { t = l(t); for (var n = e.length; n--;) { var r = e[n]; for (var i = 0; i < r.length; i++) { var s = r[i], o = v(s, t); t = o[0], o.length != 2 && (o[2] ? (r.splice(i + 1, 1, o[1], o[2]), i++) : o[1] || (r.splice(i, 1), i--)) } r.length || e.splice(n, 1) } return e } function w(e, t) { for (var n = 0; n < t.length; n++) { var r = t[n]; for (var i = 0; i < r.length; i++)b(e, r[i]) } } var r = function () { this.$maxRev = 0, this.$fromUndo = !1, this.reset() }; (function () { this.addSession = function (e) { this.$session = e }, this.add = function (e, t, n) { if (this.$fromUndo) return; if (e == this.$lastDelta) return; this.$keepRedoStack || (this.$redoStack.length = 0); if (t === !1 || !this.lastDeltas) this.lastDeltas = [], this.$undoStack.push(this.lastDeltas), e.id = this.$rev = ++this.$maxRev; if (e.action == "remove" || e.action == "insert") this.$lastDelta = e; this.lastDeltas.push(e) }, this.addSelection = function (e, t) { this.selections.push({ value: e, rev: t || this.$rev }) }, this.startNewGroup = function () { return this.lastDeltas = null, this.$rev }, this.markIgnored = function (e, t) { t == null && (t = this.$rev + 1); var n = this.$undoStack; for (var r = n.length; r--;) { var i = n[r][0]; if (i.id <= e) break; i.id < t && (i.ignore = !0) } this.lastDeltas = null }, this.getSelection = function (e, t) { var n = this.selections; for (var r = n.length; r--;) { var i = n[r]; if (i.rev < e) return t && (i = n[r + 1]), i } }, this.getRevision = function () { return this.$rev }, this.getDeltas = function (e, t) { t == null && (t = this.$rev + 1); var n = this.$undoStack, r = null, i = 0; for (var s = n.length; s--;) { var o = n[s][0]; o.id < t && !r && (r = s + 1); if (o.id <= e) { i = s + 1; break } } return n.slice(i, r) }, this.getChangedRanges = function (e, t) { t == null && (t = this.$rev + 1) }, this.getChangedLines = function (e, t) { t == null && (t = this.$rev + 1) }, this.undo = function (e, t) { this.lastDeltas = null; var n = this.$undoStack; if (!i(n, n.length)) return; e || (e = this.$session), this.$redoStackBaseRev !== this.$rev && this.$redoStack.length && (this.$redoStack = []), this.$fromUndo = !0; var r = n.pop(), s = null; return r && (s = e.undoChanges(r, t), this.$redoStack.push(r), this.$syncRev()), this.$fromUndo = !1, s }, this.redo = function (e, t) { this.lastDeltas = null, e || (e = this.$session), this.$fromUndo = !0; if (this.$redoStackBaseRev != this.$rev) { var n = this.getDeltas(this.$redoStackBaseRev, this.$rev + 1); w(this.$redoStack, n), this.$redoStackBaseRev = this.$rev, this.$redoStack.forEach(function (e) { e[0].id = ++this.$maxRev }, this) } var r = this.$redoStack.pop(), i = null; return r && (i = e.redoChanges(r, t), this.$undoStack.push(r), this.$syncRev()), this.$fromUndo = !1, i }, this.$syncRev = function () { var e = this.$undoStack, t = e[e.length - 1], n = t && t[0].id || 0; this.$redoStackBaseRev = n, this.$rev = n }, this.reset = function () { this.lastDeltas = null, this.$lastDelta = null, this.$undoStack = [], this.$redoStack = [], this.$rev = 0, this.mark = 0, this.$redoStackBaseRev = this.$rev, this.selections = [] }, this.canUndo = function () { return this.$undoStack.length > 0 }, this.canRedo = function () { return this.$redoStack.length > 0 }, this.bookmark = function (e) { e == undefined && (e = this.$rev), this.mark = e }, this.isAtBookmark = function () { return this.$rev === this.mark }, this.toJSON = function () { }, this.fromJSON = function () { }, this.hasUndo = this.canUndo, this.hasRedo = this.canRedo, this.isClean = this.isAtBookmark, this.markClean = this.bookmark, this.$prettyPrint = function (e) { return e ? c(e) : c(this.$undoStack) + "\n---\n" + c(this.$redoStack) } }).call(r.prototype); var s = e("./range").Range, o = s.comparePoints, u = s.comparePoints; t.UndoManager = r }), define("ace/layer/lines", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = function (e, t) { this.element = e, this.canvasHeight = t || 5e5, this.element.style.height = this.canvasHeight * 2 + "px", this.cells = [], this.cellCache = [], this.$offsetCoefficient = 0 }; (function () { this.moveContainer = function (e) { r.translate(this.element, 0, -(e.firstRowScreen * e.lineHeight % this.canvasHeight) - e.offset * this.$offsetCoefficient) }, this.pageChanged = function (e, t) { return Math.floor(e.firstRowScreen * e.lineHeight / this.canvasHeight) !== Math.floor(t.firstRowScreen * t.lineHeight / this.canvasHeight) }, this.computeLineTop = function (e, t, n) { var r = t.firstRowScreen * t.lineHeight, i = Math.floor(r / this.canvasHeight), s = n.documentToScreenRow(e, 0) * t.lineHeight; return s - i * this.canvasHeight }, this.computeLineHeight = function (e, t, n) { return t.lineHeight * n.getRowLineCount(e) }, this.getLength = function () { return this.cells.length }, this.get = function (e) { return this.cells[e] }, this.shift = function () { this.$cacheCell(this.cells.shift()) }, this.pop = function () { this.$cacheCell(this.cells.pop()) }, this.push = function (e) { if (Array.isArray(e)) { this.cells.push.apply(this.cells, e); var t = r.createFragment(this.element); for (var n = 0; n < e.length; n++)t.appendChild(e[n].element); this.element.appendChild(t) } else this.cells.push(e), this.element.appendChild(e.element) }, this.unshift = function (e) { if (Array.isArray(e)) { this.cells.unshift.apply(this.cells, e); var t = r.createFragment(this.element); for (var n = 0; n < e.length; n++)t.appendChild(e[n].element); this.element.firstChild ? this.element.insertBefore(t, this.element.firstChild) : this.element.appendChild(t) } else this.cells.unshift(e), this.element.insertAdjacentElement("afterbegin", e.element) }, this.last = function () { return this.cells.length ? this.cells[this.cells.length - 1] : null }, this.$cacheCell = function (e) { if (!e) return; e.element.remove(), this.cellCache.push(e) }, this.createCell = function (e, t, n, i) { var s = this.cellCache.pop(); if (!s) { var o = r.createElement("div"); i && i(o), this.element.appendChild(o), s = { element: o, text: "", row: e } } return s.row = e, s } }).call(i.prototype), t.Lines = i }), define("ace/layer/gutter", ["require", "exports", "module", "ace/lib/dom", "ace/lib/oop", "ace/lib/lang", "ace/lib/event_emitter", "ace/layer/lines"], function (e, t, n) { "use strict"; function f(e) { var t = document.createTextNode(""); e.appendChild(t); var n = r.createElement("span"); return e.appendChild(n), e } var r = e("../lib/dom"), i = e("../lib/oop"), s = e("../lib/lang"), o = e("../lib/event_emitter").EventEmitter, u = e("./lines").Lines, a = function (e) { this.element = r.createElement("div"), this.element.className = "ace_layer ace_gutter-layer", e.appendChild(this.element), this.setShowFoldWidgets(this.$showFoldWidgets), this.gutterWidth = 0, this.$annotations = [], this.$updateAnnotations = this.$updateAnnotations.bind(this), this.$lines = new u(this.element), this.$lines.$offsetCoefficient = 1 }; (function () { i.implement(this, o), this.setSession = function (e) { this.session && this.session.off("change", this.$updateAnnotations), this.session = e, e && e.on("change", this.$updateAnnotations) }, this.addGutterDecoration = function (e, t) { window.console && console.warn && console.warn("deprecated use session.addGutterDecoration"), this.session.addGutterDecoration(e, t) }, this.removeGutterDecoration = function (e, t) { window.console && console.warn && console.warn("deprecated use session.removeGutterDecoration"), this.session.removeGutterDecoration(e, t) }, this.setAnnotations = function (e) { this.$annotations = []; for (var t = 0; t < e.length; t++) { var n = e[t], r = n.row, i = this.$annotations[r]; i || (i = this.$annotations[r] = { text: [] }); var o = n.text; o = o ? s.escapeHTML(o) : n.html || "", i.text.indexOf(o) === -1 && i.text.push(o); var u = n.type; u == "error" ? i.className = " ace_error" : u == "warning" && i.className != " ace_error" ? i.className = " ace_warning" : u == "info" && !i.className && (i.className = " ace_info") } }, this.$updateAnnotations = function (e) { if (!this.$annotations.length) return; var t = e.start.row, n = e.end.row - t; if (n !== 0) if (e.action == "remove") this.$annotations.splice(t, n + 1, null); else { var r = new Array(n + 1); r.unshift(t, 1), this.$annotations.splice.apply(this.$annotations, r) } }, this.update = function (e) { this.config = e; var t = this.session, n = e.firstRow, r = Math.min(e.lastRow + e.gutterOffset, t.getLength() - 1); this.oldLastRow = r, this.config = e, this.$lines.moveContainer(e), this.$updateCursorRow(); var i = t.getNextFoldLine(n), s = i ? i.start.row : Infinity, o = null, u = -1, a = n; for (; ;) { a > s && (a = i.end.row + 1, i = t.getNextFoldLine(a, i), s = i ? i.start.row : Infinity); if (a > r) { while (this.$lines.getLength() > u + 1) this.$lines.pop(); break } o = this.$lines.get(++u), o ? o.row = a : (o = this.$lines.createCell(a, e, this.session, f), this.$lines.push(o)), this.$renderCell(o, e, i, a), a++ } this._signal("afterRender"), this.$updateGutterWidth(e) }, this.$updateGutterWidth = function (e) { var t = this.session, n = t.gutterRenderer || this.$renderer, r = t.$firstLineNumber, i = this.$lines.last() ? this.$lines.last().text : ""; if (this.$fixedWidth || t.$useWrapMode) i = t.getLength() + r - 1; var s = n ? n.getWidth(t, i, e) : i.toString().length * e.characterWidth, o = this.$padding || this.$computePadding(); s += o.left + o.right, s !== this.gutterWidth && !isNaN(s) && (this.gutterWidth = s, this.element.parentNode.style.width = this.element.style.width = Math.ceil(this.gutterWidth) + "px", this._signal("changeGutterWidth", s)) }, this.$updateCursorRow = function () { if (!this.$highlightGutterLine) return; var e = this.session.selection.getCursor(); if (this.$cursorRow === e.row) return; this.$cursorRow = e.row }, this.updateLineHighlight = function () { if (!this.$highlightGutterLine) return; var e = this.session.selection.cursor.row; this.$cursorRow = e; if (this.$cursorCell && this.$cursorCell.row == e) return; this.$cursorCell && (this.$cursorCell.element.className = this.$cursorCell.element.className.replace("ace_gutter-active-line ", "")); var t = this.$lines.cells; this.$cursorCell = null; for (var n = 0; n < t.length; n++) { var r = t[n]; if (r.row >= this.$cursorRow) { if (r.row > this.$cursorRow) { var i = this.session.getFoldLine(this.$cursorRow); if (!(n > 0 && i && i.start.row == t[n - 1].row)) break; r = t[n - 1] } r.element.className = "ace_gutter-active-line " + r.element.className, this.$cursorCell = r; break } } }, this.scrollLines = function (e) { var t = this.config; this.config = e, this.$updateCursorRow(); if (this.$lines.pageChanged(t, e)) return this.update(e); this.$lines.moveContainer(e); var n = Math.min(e.lastRow + e.gutterOffset, this.session.getLength() - 1), r = this.oldLastRow; this.oldLastRow = n; if (!t || r < e.firstRow) return this.update(e); if (n < t.firstRow) return this.update(e); if (t.firstRow < e.firstRow) for (var i = this.session.getFoldedRowCount(t.firstRow, e.firstRow - 1); i > 0; i--)this.$lines.shift(); if (r > n) for (var i = this.session.getFoldedRowCount(n + 1, r); i > 0; i--)this.$lines.pop(); e.firstRow < t.firstRow && this.$lines.unshift(this.$renderLines(e, e.firstRow, t.firstRow - 1)), n > r && this.$lines.push(this.$renderLines(e, r + 1, n)), this.updateLineHighlight(), this._signal("afterRender"), this.$updateGutterWidth(e) }, this.$renderLines = function (e, t, n) { var r = [], i = t, s = this.session.getNextFoldLine(i), o = s ? s.start.row : Infinity; for (; ;) { i > o && (i = s.end.row + 1, s = this.session.getNextFoldLine(i, s), o = s ? s.start.row : Infinity); if (i > n) break; var u = this.$lines.createCell(i, e, this.session, f); this.$renderCell(u, e, s, i), r.push(u), i++ } return r }, this.$renderCell = function (e, t, n, i) { var s = e.element, o = this.session, u = s.childNodes[0], a = s.childNodes[1], f = o.$firstLineNumber, l = o.$breakpoints, c = o.$decorations, h = o.gutterRenderer || this.$renderer, p = this.$showFoldWidgets && o.foldWidgets, d = n ? n.start.row : Number.MAX_VALUE, v = "ace_gutter-cell "; this.$highlightGutterLine && (i == this.$cursorRow || n && i < this.$cursorRow && i >= d && this.$cursorRow <= n.end.row) && (v += "ace_gutter-active-line ", this.$cursorCell != e && (this.$cursorCell && (this.$cursorCell.element.className = this.$cursorCell.element.className.replace("ace_gutter-active-line ", "")), this.$cursorCell = e)), l[i] && (v += l[i]), c[i] && (v += c[i]), this.$annotations[i] && (v += this.$annotations[i].className), s.className != v && (s.className = v); if (p) { var m = p[i]; m == null && (m = p[i] = o.getFoldWidget(i)) } if (m) { var v = "ace_fold-widget ace_" + m; m == "start" && i == d && i < n.end.row ? v += " ace_closed" : v += " ace_open", a.className != v && (a.className = v); var g = t.lineHeight + "px"; r.setStyle(a.style, "height", g), r.setStyle(a.style, "display", "inline-block") } else a && r.setStyle(a.style, "display", "none"); var y = (h ? h.getText(o, i) : i + f).toString(); return y !== u.data && (u.data = y), r.setStyle(e.element.style, "height", this.$lines.computeLineHeight(i, t, o) + "px"), r.setStyle(e.element.style, "top", this.$lines.computeLineTop(i, t, o) + "px"), e.text = y, e }, this.$fixedWidth = !1, this.$highlightGutterLine = !0, this.$renderer = "", this.setHighlightGutterLine = function (e) { this.$highlightGutterLine = e }, this.$showLineNumbers = !0, this.$renderer = "", this.setShowLineNumbers = function (e) { this.$renderer = !e && { getWidth: function () { return 0 }, getText: function () { return "" } } }, this.getShowLineNumbers = function () { return this.$showLineNumbers }, this.$showFoldWidgets = !0, this.setShowFoldWidgets = function (e) { e ? r.addCssClass(this.element, "ace_folding-enabled") : r.removeCssClass(this.element, "ace_folding-enabled"), this.$showFoldWidgets = e, this.$padding = null }, this.getShowFoldWidgets = function () { return this.$showFoldWidgets }, this.$computePadding = function () { if (!this.element.firstChild) return { left: 0, right: 0 }; var e = r.computedStyle(this.element.firstChild); return this.$padding = {}, this.$padding.left = (parseInt(e.borderLeftWidth) || 0) + (parseInt(e.paddingLeft) || 0) + 1, this.$padding.right = (parseInt(e.borderRightWidth) || 0) + (parseInt(e.paddingRight) || 0), this.$padding }, this.getRegion = function (e) { var t = this.$padding || this.$computePadding(), n = this.element.getBoundingClientRect(); if (e.x < t.left + n.left) return "markers"; if (this.$showFoldWidgets && e.x > n.right - t.right) return "foldWidgets" } }).call(a.prototype), t.Gutter = a }), define("ace/layer/marker", ["require", "exports", "module", "ace/range", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../range").Range, i = e("../lib/dom"), s = function (e) { this.element = i.createElement("div"), this.element.className = "ace_layer ace_marker-layer", e.appendChild(this.element) }; (function () { function e(e, t, n, r) { return (e ? 1 : 0) | (t ? 2 : 0) | (n ? 4 : 0) | (r ? 8 : 0) } this.$padding = 0, this.setPadding = function (e) { this.$padding = e }, this.setSession = function (e) { this.session = e }, this.setMarkers = function (e) { this.markers = e }, this.elt = function (e, t) { var n = this.i != -1 && this.element.childNodes[this.i]; n ? this.i++ : (n = document.createElement("div"), this.element.appendChild(n), this.i = -1), n.style.cssText = t, n.className = e }, this.update = function (e) { if (!e) return; this.config = e, this.i = 0; var t; for (var n in this.markers) { var r = this.markers[n]; if (!r.range) { r.update(t, this, this.session, e); continue } var i = r.range.clipRows(e.firstRow, e.lastRow); if (i.isEmpty()) continue; i = i.toScreenRange(this.session); if (r.renderer) { var s = this.$getTop(i.start.row, e), o = this.$padding + i.start.column * e.characterWidth; r.renderer(t, i, o, s, e) } else r.type == "fullLine" ? this.drawFullLineMarker(t, i, r.clazz, e) : r.type == "screenLine" ? this.drawScreenLineMarker(t, i, r.clazz, e) : i.isMultiLine() ? r.type == "text" ? this.drawTextMarker(t, i, r.clazz, e) : this.drawMultiLineMarker(t, i, r.clazz, e) : this.drawSingleLineMarker(t, i, r.clazz + " ace_start" + " ace_br15", e) } if (this.i != -1) while (this.i < this.element.childElementCount) this.element.removeChild(this.element.lastChild) }, this.$getTop = function (e, t) { return (e - t.firstRowScreen) * t.lineHeight }, this.drawTextMarker = function (t, n, i, s, o) { var u = this.session, a = n.start.row, f = n.end.row, l = a, c = 0, h = 0, p = u.getScreenLastRowColumn(l), d = new r(l, n.start.column, l, h); for (; l <= f; l++)d.start.row = d.end.row = l, d.start.column = l == a ? n.start.column : u.getRowWrapIndent(l), d.end.column = p, c = h, h = p, p = l + 1 < f ? u.getScreenLastRowColumn(l + 1) : l == f ? 0 : n.end.column, this.drawSingleLineMarker(t, d, i + (l == a ? " ace_start" : "") + " ace_br" + e(l == a || l == a + 1 && n.start.column, c < h, h > p, l == f), s, l == f ? 0 : 1, o) }, this.drawMultiLineMarker = function (e, t, n, r, i) { var s = this.$padding, o = r.lineHeight, u = this.$getTop(t.start.row, r), a = s + t.start.column * r.characterWidth; i = i || ""; if (this.session.$bidiHandler.isBidiRow(t.start.row)) { var f = t.clone(); f.end.row = f.start.row, f.end.column = this.session.getLine(f.start.row).length, this.drawBidiSingleLineMarker(e, f, n + " ace_br1 ace_start", r, null, i) } else this.elt(n + " ace_br1 ace_start", "height:" + o + "px;" + "right:0;" + "top:" + u + "px;left:" + a + "px;" + (i || "")); if (this.session.$bidiHandler.isBidiRow(t.end.row)) { var f = t.clone(); f.start.row = f.end.row, f.start.column = 0, this.drawBidiSingleLineMarker(e, f, n + " ace_br12", r, null, i) } else { u = this.$getTop(t.end.row, r); var l = t.end.column * r.characterWidth; this.elt(n + " ace_br12", "height:" + o + "px;" + "width:" + l + "px;" + "top:" + u + "px;" + "left:" + s + "px;" + (i || "")) } o = (t.end.row - t.start.row - 1) * r.lineHeight; if (o <= 0) return; u = this.$getTop(t.start.row + 1, r); var c = (t.start.column ? 1 : 0) | (t.end.column ? 0 : 8); this.elt(n + (c ? " ace_br" + c : ""), "height:" + o + "px;" + "right:0;" + "top:" + u + "px;" + "left:" + s + "px;" + (i || "")) }, this.drawSingleLineMarker = function (e, t, n, r, i, s) { if (this.session.$bidiHandler.isBidiRow(t.start.row)) return this.drawBidiSingleLineMarker(e, t, n, r, i, s); var o = r.lineHeight, u = (t.end.column + (i || 0) - t.start.column) * r.characterWidth, a = this.$getTop(t.start.row, r), f = this.$padding + t.start.column * r.characterWidth; this.elt(n, "height:" + o + "px;" + "width:" + u + "px;" + "top:" + a + "px;" + "left:" + f + "px;" + (s || "")) }, this.drawBidiSingleLineMarker = function (e, t, n, r, i, s) { var o = r.lineHeight, u = this.$getTop(t.start.row, r), a = this.$padding, f = this.session.$bidiHandler.getSelections(t.start.column, t.end.column); f.forEach(function (e) { this.elt(n, "height:" + o + "px;" + "width:" + e.width + (i || 0) + "px;" + "top:" + u + "px;" + "left:" + (a + e.left) + "px;" + (s || "")) }, this) }, this.drawFullLineMarker = function (e, t, n, r, i) { var s = this.$getTop(t.start.row, r), o = r.lineHeight; t.start.row != t.end.row && (o += this.$getTop(t.end.row, r) - s), this.elt(n, "height:" + o + "px;" + "top:" + s + "px;" + "left:0;right:0;" + (i || "")) }, this.drawScreenLineMarker = function (e, t, n, r, i) { var s = this.$getTop(t.start.row, r), o = r.lineHeight; this.elt(n, "height:" + o + "px;" + "top:" + s + "px;" + "left:0;right:0;" + (i || "")) } }).call(s.prototype), t.Marker = s }), define("ace/layer/text", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/layer/lines", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("../lib/dom"), s = e("../lib/lang"), o = e("./lines").Lines, u = e("../lib/event_emitter").EventEmitter, a = function (e) { this.dom = i, this.element = this.dom.createElement("div"), this.element.className = "ace_layer ace_text-layer", e.appendChild(this.element), this.$updateEolChar = this.$updateEolChar.bind(this), this.$lines = new o(this.element) }; (function () { r.implement(this, u), this.EOF_CHAR = "\u00b6", this.EOL_CHAR_LF = "\u00ac", this.EOL_CHAR_CRLF = "\u00a4", this.EOL_CHAR = this.EOL_CHAR_LF, this.TAB_CHAR = "\u2014", this.SPACE_CHAR = "\u00b7", this.$padding = 0, this.MAX_LINE_LENGTH = 1e4, this.$updateEolChar = function () { var e = this.session.doc, t = e.getNewLineCharacter() == "\n" && e.getNewLineMode() != "windows", n = t ? this.EOL_CHAR_LF : this.EOL_CHAR_CRLF; if (this.EOL_CHAR != n) return this.EOL_CHAR = n, !0 }, this.setPadding = function (e) { this.$padding = e, this.element.style.margin = "0 " + e + "px" }, this.getLineHeight = function () { return this.$fontMetrics.$characterSize.height || 0 }, this.getCharacterWidth = function () { return this.$fontMetrics.$characterSize.width || 0 }, this.$setFontMetrics = function (e) { this.$fontMetrics = e, this.$fontMetrics.on("changeCharacterSize", function (e) { this._signal("changeCharacterSize", e) }.bind(this)), this.$pollSizeChanges() }, this.checkForSizeChanges = function () { this.$fontMetrics.checkForSizeChanges() }, this.$pollSizeChanges = function () { return this.$pollSizeChangesTimer = this.$fontMetrics.$pollSizeChanges() }, this.setSession = function (e) { this.session = e, e && this.$computeTabString() }, this.showInvisibles = !1, this.showSpaces = !1, this.showTabs = !1, this.showEOL = !1, this.setShowInvisibles = function (e) { return this.showInvisibles == e ? !1 : (this.showInvisibles = e, typeof e == "string" ? (this.showSpaces = /tab/i.test(e), this.showTabs = /space/i.test(e), this.showEOL = /eol/i.test(e)) : this.showSpaces = this.showTabs = this.showEOL = e, this.$computeTabString(), !0) }, this.displayIndentGuides = !0, this.setDisplayIndentGuides = function (e) { return this.displayIndentGuides == e ? !1 : (this.displayIndentGuides = e, this.$computeTabString(), !0) }, this.$tabStrings = [], this.onChangeTabSize = this.$computeTabString = function () { var e = this.session.getTabSize(); this.tabSize = e; var t = this.$tabStrings = [0]; for (var n = 1; n < e + 1; n++)if (this.showTabs) { var r = this.dom.createElement("span"); r.className = "ace_invisible ace_invisible_tab", r.textContent = s.stringRepeat(this.TAB_CHAR, n), t.push(r) } else t.push(this.dom.createTextNode(s.stringRepeat(" ", n), this.element)); if (this.displayIndentGuides) { this.$indentGuideRe = /\s\S| \t|\t |\s$/; var i = "ace_indent-guide", o = this.showSpaces ? " ace_invisible ace_invisible_space" : "", u = this.showSpaces ? s.stringRepeat(this.SPACE_CHAR, this.tabSize) : s.stringRepeat(" ", this.tabSize), a = this.showTabs ? " ace_invisible ace_invisible_tab" : "", f = this.showTabs ? s.stringRepeat(this.TAB_CHAR, this.tabSize) : u, r = this.dom.createElement("span"); r.className = i + o, r.textContent = u, this.$tabStrings[" "] = r; var r = this.dom.createElement("span"); r.className = i + a, r.textContent = f, this.$tabStrings[" "] = r } }, this.updateLines = function (e, t, n) { if (this.config.lastRow != e.lastRow || this.config.firstRow != e.firstRow) return this.update(e); this.config = e; var r = Math.max(t, e.firstRow), i = Math.min(n, e.lastRow), s = this.element.childNodes, o = 0; for (var u = e.firstRow; u < r; u++) { var a = this.session.getFoldLine(u); if (a) { if (a.containsRow(r)) { r = a.start.row; break } u = a.end.row } o++ } var f = !1, u = r, a = this.session.getNextFoldLine(u), l = a ? a.start.row : Infinity; for (; ;) { u > l && (u = a.end.row + 1, a = this.session.getNextFoldLine(u, a), l = a ? a.start.row : Infinity); if (u > i) break; var c = s[o++]; if (c) { this.dom.removeChildren(c), this.$renderLine(c, u, u == l ? a : !1), f && (c.style.top = this.$lines.computeLineTop(u, e, this.session) + "px"); var h = e.lineHeight * this.session.getRowLength(u) + "px"; c.style.height != h && (f = !0, c.style.height = h) } u++ } if (f) while (o < this.$lines.cells.length) { var p = this.$lines.cells[o++]; p.element.style.top = this.$lines.computeLineTop(p.row, e, this.session) + "px" } }, this.scrollLines = function (e) { var t = this.config; this.config = e; if (this.$lines.pageChanged(t, e)) return this.update(e); this.$lines.moveContainer(e); var n = e.lastRow, r = t ? t.lastRow : -1; if (!t || r < e.firstRow) return this.update(e); if (n < t.firstRow) return this.update(e); if (!t || t.lastRow < e.firstRow) return this.update(e); if (e.lastRow < t.firstRow) return this.update(e); if (t.firstRow < e.firstRow) for (var i = this.session.getFoldedRowCount(t.firstRow, e.firstRow - 1); i > 0; i--)this.$lines.shift(); if (t.lastRow > e.lastRow) for (var i = this.session.getFoldedRowCount(e.lastRow + 1, t.lastRow); i > 0; i--)this.$lines.pop(); e.firstRow < t.firstRow && this.$lines.unshift(this.$renderLinesFragment(e, e.firstRow, t.firstRow - 1)), e.lastRow > t.lastRow && this.$lines.push(this.$renderLinesFragment(e, t.lastRow + 1, e.lastRow)) }, this.$renderLinesFragment = function (e, t, n) { var r = [], s = t, o = this.session.getNextFoldLine(s), u = o ? o.start.row : Infinity; for (; ;) { s > u && (s = o.end.row + 1, o = this.session.getNextFoldLine(s, o), u = o ? o.start.row : Infinity); if (s > n) break; var a = this.$lines.createCell(s, e, this.session), f = a.element; this.dom.removeChildren(f), i.setStyle(f.style, "height", this.$lines.computeLineHeight(s, e, this.session) + "px"), i.setStyle(f.style, "top", this.$lines.computeLineTop(s, e, this.session) + "px"), this.$renderLine(f, s, s == u ? o : !1), this.$useLineGroups() ? f.className = "ace_line_group" : f.className = "ace_line", r.push(a), s++ } return r }, this.update = function (e) { this.$lines.moveContainer(e), this.config = e; var t = e.firstRow, n = e.lastRow, r = this.$lines; while (r.getLength()) r.pop(); r.push(this.$renderLinesFragment(e, t, n)) }, this.$textToken = { text: !0, rparen: !0, lparen: !0 }, this.$renderToken = function (e, t, n, r) { var i = this, o = /(\t)|( +)|([\x00-\x1f\x80-\xa0\xad\u1680\u180E\u2000-\u200f\u2028\u2029\u202F\u205F\uFEFF\uFFF9-\uFFFC]+)|(\u3000)|([\u1100-\u115F\u11A3-\u11A7\u11FA-\u11FF\u2329-\u232A\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3001-\u303E\u3041-\u3096\u3099-\u30FF\u3105-\u312D\u3131-\u318E\u3190-\u31BA\u31C0-\u31E3\u31F0-\u321E\u3220-\u3247\u3250-\u32FE\u3300-\u4DBF\u4E00-\uA48C\uA490-\uA4C6\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFAFF\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFF01-\uFF60\uFFE0-\uFFE6]|[\uD800-\uDBFF][\uDC00-\uDFFF])/g, u = this.dom.createFragment(this.element), a, f = 0; while (a = o.exec(r)) { var l = a[1], c = a[2], h = a[3], p = a[4], d = a[5]; if (!i.showSpaces && c) continue; var v = f != a.index ? r.slice(f, a.index) : ""; f = a.index + a[0].length, v && u.appendChild(this.dom.createTextNode(v, this.element)); if (l) { var m = i.session.getScreenTabSize(t + a.index); u.appendChild(i.$tabStrings[m].cloneNode(!0)), t += m - 1 } else if (c) if (i.showSpaces) { var g = this.dom.createElement("span"); g.className = "ace_invisible ace_invisible_space", g.textContent = s.stringRepeat(i.SPACE_CHAR, c.length), u.appendChild(g) } else u.appendChild(this.com.createTextNode(c, this.element)); else if (h) { var g = this.dom.createElement("span"); g.className = "ace_invisible ace_invisible_space ace_invalid", g.textContent = s.stringRepeat(i.SPACE_CHAR, h.length), u.appendChild(g) } else if (p) { t += 1; var g = this.dom.createElement("span"); g.style.width = i.config.characterWidth * 2 + "px", g.className = i.showSpaces ? "ace_cjk ace_invisible ace_invisible_space" : "ace_cjk", g.textContent = i.showSpaces ? i.SPACE_CHAR : p, u.appendChild(g) } else if (d) { t += 1; var g = this.dom.createElement("span"); g.style.width = i.config.characterWidth * 2 + "px", g.className = "ace_cjk", g.textContent = d, u.appendChild(g) } } u.appendChild(this.dom.createTextNode(f ? r.slice(f) : r, this.element)); if (!this.$textToken[n.type]) { var y = "ace_" + n.type.replace(/\./g, " ace_"), g = this.dom.createElement("span"); n.type == "fold" && (g.style.width = n.value.length * this.config.characterWidth + "px"), g.className = y, g.appendChild(u), e.appendChild(g) } else e.appendChild(u); return t + r.length }, this.renderIndentGuide = function (e, t, n) { var r = t.search(this.$indentGuideRe); if (r <= 0 || r >= n) return t; if (t[0] == " ") { r -= r % this.tabSize; var i = r / this.tabSize; for (var s = 0; s < i; s++)e.appendChild(this.$tabStrings[" "].cloneNode(!0)); return t.substr(r) } if (t[0] == " ") { for (var s = 0; s < r; s++)e.appendChild(this.$tabStrings[" "].cloneNode(!0)); return t.substr(r) } return t }, this.$createLineElement = function (e) { var t = this.dom.createElement("div"); return t.className = "ace_line", t.style.height = this.config.lineHeight + "px", t }, this.$renderWrappedLine = function (e, t, n) { var r = 0, i = 0, o = n[0], u = 0, a = this.$createLineElement(); e.appendChild(a); for (var f = 0; f < t.length; f++) { var l = t[f], c = l.value; if (f == 0 && this.displayIndentGuides) { r = c.length, c = this.renderIndentGuide(a, c, o); if (!c) continue; r -= c.length } if (r + c.length < o) u = this.$renderToken(a, u, l, c), r += c.length; else { while (r + c.length >= o) u = this.$renderToken(a, u, l, c.substring(0, o - r)), c = c.substring(o - r), r = o, a = this.$createLineElement(), e.appendChild(a), a.appendChild(this.dom.createTextNode(s.stringRepeat("\u00a0", n.indent), this.element)), i++, u = 0, o = n[i] || Number.MAX_VALUE; c.length != 0 && (r += c.length, u = this.$renderToken(a, u, l, c)) } } n[n.length - 1] > this.MAX_LINE_LENGTH && this.$renderOverflowMessage(a, u, null, "", !0) }, this.$renderSimpleLine = function (e, t) { var n = 0, r = t[0], i = r.value; this.displayIndentGuides && (i = this.renderIndentGuide(e, i)), i && (n = this.$renderToken(e, n, r, i)); for (var s = 1; s < t.length; s++) { r = t[s], i = r.value; if (n + i.length > this.MAX_LINE_LENGTH) return this.$renderOverflowMessage(e, n, r, i); n = this.$renderToken(e, n, r, i) } }, this.$renderOverflowMessage = function (e, t, n, r, i) { n && this.$renderToken(e, t, n, r.slice(0, this.MAX_LINE_LENGTH - t)); var s = this.dom.createElement("span"); s.className = "ace_inline_button ace_keyword ace_toggle_wrap", s.textContent = i ? "" : "", e.appendChild(s) }, this.$renderLine = function (e, t, n) { !n && n != 0 && (n = this.session.getFoldLine(t)); if (n) var r = this.$getFoldLineTokens(t, n); else var r = this.session.getTokens(t); var i = e; if (r.length) { var s = this.session.getRowSplitData(t); if (s && s.length) { this.$renderWrappedLine(e, r, s); var i = e.lastChild } else { var i = e; this.$useLineGroups() && (i = this.$createLineElement(), e.appendChild(i)), this.$renderSimpleLine(i, r) } } else this.$useLineGroups() && (i = this.$createLineElement(), e.appendChild(i)); if (this.showEOL && i) { n && (t = n.end.row); var o = this.dom.createElement("span"); o.className = "ace_invisible ace_invisible_eol", o.textContent = t == this.session.getLength() - 1 ? this.EOF_CHAR : this.EOL_CHAR, i.appendChild(o) } }, this.$getFoldLineTokens = function (e, t) { function i(e, t, n) { var i = 0, s = 0; while (s + e[i].value.length < t) { s += e[i].value.length, i++; if (i == e.length) return } if (s != t) { var o = e[i].value.substring(t - s); o.length > n - t && (o = o.substring(0, n - t)), r.push({ type: e[i].type, value: o }), s = t + o.length, i += 1 } while (s < n && i < e.length) { var o = e[i].value; o.length + s > n ? r.push({ type: e[i].type, value: o.substring(0, n - s) }) : r.push(e[i]), s += o.length, i += 1 } } var n = this.session, r = [], s = n.getTokens(e); return t.walk(function (e, t, o, u, a) { e != null ? r.push({ type: "fold", value: e }) : (a && (s = n.getTokens(t)), s.length && i(s, u, o)) }, t.end.row, this.session.getLine(t.end.row).length), r }, this.$useLineGroups = function () { return this.session.getUseWrapMode() }, this.destroy = function () { } }).call(a.prototype), t.Text = a }), define("ace/layer/cursor", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = function (e) { this.element = r.createElement("div"), this.element.className = "ace_layer ace_cursor-layer", e.appendChild(this.element), this.isVisible = !1, this.isBlinking = !0, this.blinkInterval = 1e3, this.smoothBlinking = !1, this.cursors = [], this.cursor = this.addCursor(), r.addCssClass(this.element, "ace_hidden-cursors"), this.$updateCursors = this.$updateOpacity.bind(this) }; (function () { this.$updateOpacity = function (e) { var t = this.cursors; for (var n = t.length; n--;)r.setStyle(t[n].style, "opacity", e ? "" : "0") }, this.$startCssAnimation = function () { var e = this.cursors; for (var t = e.length; t--;)e[t].style.animationDuration = this.blinkInterval + "ms"; setTimeout(function () { r.addCssClass(this.element, "ace_animate-blinking") }.bind(this)) }, this.$stopCssAnimation = function () { r.removeCssClass(this.element, "ace_animate-blinking") }, this.$padding = 0, this.setPadding = function (e) { this.$padding = e }, this.setSession = function (e) { this.session = e }, this.setBlinking = function (e) { e != this.isBlinking && (this.isBlinking = e, this.restartTimer()) }, this.setBlinkInterval = function (e) { e != this.blinkInterval && (this.blinkInterval = e, this.restartTimer()) }, this.setSmoothBlinking = function (e) { e != this.smoothBlinking && (this.smoothBlinking = e, r.setCssClass(this.element, "ace_smooth-blinking", e), this.$updateCursors(!0), this.restartTimer()) }, this.addCursor = function () { var e = r.createElement("div"); return e.className = "ace_cursor", this.element.appendChild(e), this.cursors.push(e), e }, this.removeCursor = function () { if (this.cursors.length > 1) { var e = this.cursors.pop(); return e.parentNode.removeChild(e), e } }, this.hideCursor = function () { this.isVisible = !1, r.addCssClass(this.element, "ace_hidden-cursors"), this.restartTimer() }, this.showCursor = function () { this.isVisible = !0, r.removeCssClass(this.element, "ace_hidden-cursors"), this.restartTimer() }, this.restartTimer = function () { var e = this.$updateCursors; clearInterval(this.intervalId), clearTimeout(this.timeoutId), this.$stopCssAnimation(), this.smoothBlinking && r.removeCssClass(this.element, "ace_smooth-blinking"), e(!0); if (!this.isBlinking || !this.blinkInterval || !this.isVisible) { this.$stopCssAnimation(); return } this.smoothBlinking && setTimeout(function () { r.addCssClass(this.element, "ace_smooth-blinking") }.bind(this)); if (r.HAS_CSS_ANIMATION) this.$startCssAnimation(); else { var t = function () { this.timeoutId = setTimeout(function () { e(!1) }, .6 * this.blinkInterval) }.bind(this); this.intervalId = setInterval(function () { e(!0), t() }, this.blinkInterval), t() } }, this.getPixelPosition = function (e, t) { if (!this.config || !this.session) return { left: 0, top: 0 }; e || (e = this.session.selection.getCursor()); var n = this.session.documentToScreenPosition(e), r = this.$padding + (this.session.$bidiHandler.isBidiRow(n.row, e.row) ? this.session.$bidiHandler.getPosLeft(n.column) : n.column * this.config.characterWidth), i = (n.row - (t ? this.config.firstRowScreen : 0)) * this.config.lineHeight; return { left: r, top: i } }, this.isCursorInView = function (e, t) { return e.top >= 0 && e.top < t.maxHeight }, this.update = function (e) { this.config = e; var t = this.session.$selectionMarkers, n = 0, i = 0; if (t === undefined || t.length === 0) t = [{ cursor: null }]; for (var n = 0, s = t.length; n < s; n++) { var o = this.getPixelPosition(t[n].cursor, !0); if ((o.top > e.height + e.offset || o.top < 0) && n > 1) continue; var u = this.cursors[i++] || this.addCursor(), a = u.style; this.drawCursor ? this.drawCursor(u, o, e, t[n], this.session) : this.isCursorInView(o, e) ? (r.setStyle(a, "display", "block"), r.translate(u, o.left, o.top), r.setStyle(a, "width", Math.round(e.characterWidth) + "px"), r.setStyle(a, "height", e.lineHeight + "px")) : r.setStyle(a, "display", "none") } while (this.cursors.length > i) this.removeCursor(); var f = this.session.getOverwrite(); this.$setOverwrite(f), this.$pixelPos = o, this.restartTimer() }, this.drawCursor = null, this.$setOverwrite = function (e) { e != this.overwrite && (this.overwrite = e, e ? r.addCssClass(this.element, "ace_overwrite-cursors") : r.removeCssClass(this.element, "ace_overwrite-cursors")) }, this.destroy = function () { clearInterval(this.intervalId), clearTimeout(this.timeoutId) } }).call(i.prototype), t.Cursor = i }), define("ace/scrollbar", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/event", "ace/lib/event_emitter"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./lib/event"), o = e("./lib/event_emitter").EventEmitter, u = 32768, a = function (e) { this.element = i.createElement("div"), this.element.className = "ace_scrollbar ace_scrollbar" + this.classSuffix, this.inner = i.createElement("div"), this.inner.className = "ace_scrollbar-inner", this.inner.textContent = "\u00a0", this.element.appendChild(this.inner), e.appendChild(this.element), this.setVisible(!1), this.skipEvent = !1, s.addListener(this.element, "scroll", this.onScroll.bind(this)), s.addListener(this.element, "mousedown", s.preventDefault) }; (function () { r.implement(this, o), this.setVisible = function (e) { this.element.style.display = e ? "" : "none", this.isVisible = e, this.coeff = 1 } }).call(a.prototype); var f = function (e, t) { a.call(this, e), this.scrollTop = 0, this.scrollHeight = 0, t.$scrollbarWidth = this.width = i.scrollbarWidth(e.ownerDocument), this.inner.style.width = this.element.style.width = (this.width || 15) + 5 + "px", this.$minWidth = 0 }; r.inherits(f, a), function () { this.classSuffix = "-v", this.onScroll = function () { if (!this.skipEvent) { this.scrollTop = this.element.scrollTop; if (this.coeff != 1) { var e = this.element.clientHeight / this.scrollHeight; this.scrollTop = this.scrollTop * (1 - e) / (this.coeff - e) } this._emit("scroll", { data: this.scrollTop }) } this.skipEvent = !1 }, this.getWidth = function () { return Math.max(this.isVisible ? this.width : 0, this.$minWidth || 0) }, this.setHeight = function (e) { this.element.style.height = e + "px" }, this.setInnerHeight = this.setScrollHeight = function (e) { this.scrollHeight = e, e > u ? (this.coeff = u / e, e = u) : this.coeff != 1 && (this.coeff = 1), this.inner.style.height = e + "px" }, this.setScrollTop = function (e) { this.scrollTop != e && (this.skipEvent = !0, this.scrollTop = e, this.element.scrollTop = e * this.coeff) } }.call(f.prototype); var l = function (e, t) { a.call(this, e), this.scrollLeft = 0, this.height = t.$scrollbarWidth, this.inner.style.height = this.element.style.height = (this.height || 15) + 5 + "px" }; r.inherits(l, a), function () { this.classSuffix = "-h", this.onScroll = function () { this.skipEvent || (this.scrollLeft = this.element.scrollLeft, this._emit("scroll", { data: this.scrollLeft })), this.skipEvent = !1 }, this.getHeight = function () { return this.isVisible ? this.height : 0 }, this.setWidth = function (e) { this.element.style.width = e + "px" }, this.setInnerWidth = function (e) { this.inner.style.width = e + "px" }, this.setScrollWidth = function (e) { this.inner.style.width = e + "px" }, this.setScrollLeft = function (e) { this.scrollLeft != e && (this.skipEvent = !0, this.scrollLeft = this.element.scrollLeft = e) } }.call(l.prototype), t.ScrollBar = f, t.ScrollBarV = f, t.ScrollBarH = l, t.VScrollBar = f, t.HScrollBar = l }), define("ace/renderloop", ["require", "exports", "module", "ace/lib/event"], function (e, t, n) { "use strict"; var r = e("./lib/event"), i = function (e, t) { this.onRender = e, this.pending = !1, this.changes = 0, this.$recursionLimit = 2, this.window = t || window; var n = this; this._flush = function (e) { n.pending = !1; var t = n.changes; t && (r.blockIdle(100), n.changes = 0, n.onRender(t)); if (n.changes) { if (n.$recursionLimit-- < 0) return; n.schedule() } else n.$recursionLimit = 2 } }; (function () { this.schedule = function (e) { this.changes = this.changes | e, this.changes && !this.pending && (r.nextFrame(this._flush), this.pending = !0) }, this.clear = function (e) { var t = this.changes; return this.changes = 0, t } }).call(i.prototype), t.RenderLoop = i }), define("ace/layer/font_metrics", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/lib/lang", "ace/lib/event", "ace/lib/useragent", "ace/lib/event_emitter"], function (e, t, n) { var r = e("../lib/oop"), i = e("../lib/dom"), s = e("../lib/lang"), o = e("../lib/event"), u = e("../lib/useragent"), a = e("../lib/event_emitter").EventEmitter, f = 256, l = typeof ResizeObserver == "function", c = 200, h = t.FontMetrics = function (e) { this.el = i.createElement("div"), this.$setMeasureNodeStyles(this.el.style, !0), this.$main = i.createElement("div"), this.$setMeasureNodeStyles(this.$main.style), this.$measureNode = i.createElement("div"), this.$setMeasureNodeStyles(this.$measureNode.style), this.el.appendChild(this.$main), this.el.appendChild(this.$measureNode), e.appendChild(this.el), this.$measureNode.textContent = s.stringRepeat("X", f), this.$characterSize = { width: 0, height: 0 }, l ? this.$addObserver() : this.checkForSizeChanges() }; (function () { r.implement(this, a), this.$characterSize = { width: 0, height: 0 }, this.$setMeasureNodeStyles = function (e, t) { e.width = e.height = "auto", e.left = e.top = "0px", e.visibility = "hidden", e.position = "absolute", e.whiteSpace = "pre", u.isIE < 8 ? e["font-family"] = "inherit" : e.font = "inherit", e.overflow = t ? "hidden" : "visible" }, this.checkForSizeChanges = function (e) { e === undefined && (e = this.$measureSizes()); if (e && (this.$characterSize.width !== e.width || this.$characterSize.height !== e.height)) { this.$measureNode.style.fontWeight = "bold"; var t = this.$measureSizes(); this.$measureNode.style.fontWeight = "", this.$characterSize = e, this.charSizes = Object.create(null), this.allowBoldFonts = t && t.width === e.width && t.height === e.height, this._emit("changeCharacterSize", { data: e }) } }, this.$addObserver = function () { var e = this; this.$observer = new window.ResizeObserver(function (t) { e.checkForSizeChanges() }), this.$observer.observe(this.$measureNode) }, this.$pollSizeChanges = function () { if (this.$pollSizeChangesTimer || this.$observer) return this.$pollSizeChangesTimer; var e = this; return this.$pollSizeChangesTimer = o.onIdle(function t() { e.checkForSizeChanges(), o.onIdle(t, 500) }, 500) }, this.setPolling = function (e) { e ? this.$pollSizeChanges() : this.$pollSizeChangesTimer && (clearInterval(this.$pollSizeChangesTimer), this.$pollSizeChangesTimer = 0) }, this.$measureSizes = function (e) { var t = { height: (e || this.$measureNode).clientHeight, width: (e || this.$measureNode).clientWidth / f }; return t.width === 0 || t.height === 0 ? null : t }, this.$measureCharWidth = function (e) { this.$main.textContent = s.stringRepeat(e, f); var t = this.$main.getBoundingClientRect(); return t.width / f }, this.getCharacterWidth = function (e) { var t = this.charSizes[e]; return t === undefined && (t = this.charSizes[e] = this.$measureCharWidth(e) / this.$characterSize.width), t }, this.destroy = function () { clearInterval(this.$pollSizeChangesTimer), this.$observer && this.$observer.disconnect(), this.el && this.el.parentNode && this.el.parentNode.removeChild(this.el) }, this.$getZoom = function e(t) { return t ? (window.getComputedStyle(t).zoom || 1) * e(t.parentElement) : 1 }, this.$initTransformMeasureNodes = function () { var e = function (e, t) { return ["div", { style: "position: absolute;top:" + e + "px;left:" + t + "px;" }] }; this.els = i.buildDom([e(0, 0), e(c, 0), e(0, c), e(c, c)], this.el) }, this.transformCoordinates = function (e, t) { function r(e, t, n) { var r = e[1] * t[0] - e[0] * t[1]; return [(-t[1] * n[0] + t[0] * n[1]) / r, (+e[1] * n[0] - e[0] * n[1]) / r] } function i(e, t) { return [e[0] - t[0], e[1] - t[1]] } function s(e, t) { return [e[0] + t[0], e[1] + t[1]] } function o(e, t) { return [e * t[0], e * t[1]] } function u(e) { var t = e.getBoundingClientRect(); return [t.left, t.top] } if (e) { var n = this.$getZoom(this.el); e = o(1 / n, e) } this.els || this.$initTransformMeasureNodes(); var a = u(this.els[0]), f = u(this.els[1]), l = u(this.els[2]), h = u(this.els[3]), p = r(i(h, f), i(h, l), i(s(f, l), s(h, a))), d = o(1 + p[0], i(f, a)), v = o(1 + p[1], i(l, a)); if (t) { var m = t, g = p[0] * m[0] / c + p[1] * m[1] / c + 1, y = s(o(m[0], d), o(m[1], v)); return s(o(1 / g / c, y), a) } var b = i(e, a), w = r(i(d, o(p[0], b)), i(v, o(p[1], b)), b); return o(c, w) } }).call(h.prototype) }), define("ace/virtual_renderer", ["require", "exports", "module", "ace/lib/oop", "ace/lib/dom", "ace/config", "ace/layer/gutter", "ace/layer/marker", "ace/layer/text", "ace/layer/cursor", "ace/scrollbar", "ace/scrollbar", "ace/renderloop", "ace/layer/font_metrics", "ace/lib/event_emitter", "ace/lib/useragent"], function (e, t, n) { "use strict"; var r = e("./lib/oop"), i = e("./lib/dom"), s = e("./config"), o = e("./layer/gutter").Gutter, u = e("./layer/marker").Marker, a = e("./layer/text").Text, f = e("./layer/cursor").Cursor, l = e("./scrollbar").HScrollBar, c = e("./scrollbar").VScrollBar, h = e("./renderloop").RenderLoop, p = e("./layer/font_metrics").FontMetrics, d = e("./lib/event_emitter").EventEmitter, v = '.ace_br1 {border-top-left-radius : 3px;}.ace_br2 {border-top-right-radius : 3px;}.ace_br3 {border-top-left-radius : 3px; border-top-right-radius: 3px;}.ace_br4 {border-bottom-right-radius: 3px;}.ace_br5 {border-top-left-radius : 3px; border-bottom-right-radius: 3px;}.ace_br6 {border-top-right-radius : 3px; border-bottom-right-radius: 3px;}.ace_br7 {border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px;}.ace_br8 {border-bottom-left-radius : 3px;}.ace_br9 {border-top-left-radius : 3px; border-bottom-left-radius: 3px;}.ace_br10{border-top-right-radius : 3px; border-bottom-left-radius: 3px;}.ace_br11{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br12{border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br13{border-top-left-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br14{border-top-right-radius : 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_br15{border-top-left-radius : 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px;}.ace_editor {position: relative;overflow: hidden;padding: 0;font: 12px/normal \'Monaco\', \'Menlo\', \'Ubuntu Mono\', \'Consolas\', \'source-code-pro\', monospace;direction: ltr;text-align: left;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);}.ace_scroller {position: absolute;overflow: hidden;top: 0;bottom: 0;background-color: inherit;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;cursor: text;}.ace_content {position: absolute;box-sizing: border-box;min-width: 100%;contain: style size layout;font-variant-ligatures: no-common-ligatures;}.ace_dragging .ace_scroller:before{position: absolute;top: 0;left: 0;right: 0;bottom: 0;content: \'\';background: rgba(250, 250, 250, 0.01);z-index: 1000;}.ace_dragging.ace_dark .ace_scroller:before{background: rgba(0, 0, 0, 0.01);}.ace_selecting, .ace_selecting * {cursor: text !important;}.ace_gutter {position: absolute;overflow : hidden;width: auto;top: 0;bottom: 0;left: 0;cursor: default;z-index: 4;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;contain: style size layout;}.ace_gutter-active-line {position: absolute;left: 0;right: 0;}.ace_scroller.ace_scroll-left {box-shadow: 17px 0 16px -16px rgba(0, 0, 0, 0.4) inset;}.ace_gutter-cell {position: absolute;top: 0;left: 0;right: 0;padding-left: 19px;padding-right: 6px;background-repeat: no-repeat;}.ace_gutter-cell.ace_error {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAABOFBMVEX/////////QRswFAb/Ui4wFAYwFAYwFAaWGAfDRymzOSH/PxswFAb/SiUwFAYwFAbUPRvjQiDllog5HhHdRybsTi3/Tyv9Tir+Syj/UC3////XurebMBIwFAb/RSHbPx/gUzfdwL3kzMivKBAwFAbbvbnhPx66NhowFAYwFAaZJg8wFAaxKBDZurf/RB6mMxb/SCMwFAYwFAbxQB3+RB4wFAb/Qhy4Oh+4QifbNRcwFAYwFAYwFAb/QRzdNhgwFAYwFAbav7v/Uy7oaE68MBK5LxLewr/r2NXewLswFAaxJw4wFAbkPRy2PyYwFAaxKhLm1tMwFAazPiQwFAaUGAb/QBrfOx3bvrv/VC/maE4wFAbRPBq6MRO8Qynew8Dp2tjfwb0wFAbx6eju5+by6uns4uH9/f36+vr/GkHjAAAAYnRSTlMAGt+64rnWu/bo8eAA4InH3+DwoN7j4eLi4xP99Nfg4+b+/u9B/eDs1MD1mO7+4PHg2MXa347g7vDizMLN4eG+Pv7i5evs/v79yu7S3/DV7/498Yv24eH+4ufQ3Ozu/v7+y13sRqwAAADLSURBVHjaZc/XDsFgGIBhtDrshlitmk2IrbHFqL2pvXf/+78DPokj7+Fz9qpU/9UXJIlhmPaTaQ6QPaz0mm+5gwkgovcV6GZzd5JtCQwgsxoHOvJO15kleRLAnMgHFIESUEPmawB9ngmelTtipwwfASilxOLyiV5UVUyVAfbG0cCPHig+GBkzAENHS0AstVF6bacZIOzgLmxsHbt2OecNgJC83JERmePUYq8ARGkJx6XtFsdddBQgZE2nPR6CICZhawjA4Fb/chv+399kfR+MMMDGOQAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: 2px center;}.ace_gutter-cell.ace_warning {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAmVBMVEX///8AAAD///8AAAAAAABPSzb/5sAAAAB/blH/73z/ulkAAAAAAAD85pkAAAAAAAACAgP/vGz/rkDerGbGrV7/pkQICAf////e0IsAAAD/oED/qTvhrnUAAAD/yHD/njcAAADuv2r/nz//oTj/p064oGf/zHAAAAA9Nir/tFIAAAD/tlTiuWf/tkIAAACynXEAAAAAAAAtIRW7zBpBAAAAM3RSTlMAABR1m7RXO8Ln31Z36zT+neXe5OzooRDfn+TZ4p3h2hTf4t3k3ucyrN1K5+Xaks52Sfs9CXgrAAAAjklEQVR42o3PbQ+CIBQFYEwboPhSYgoYunIqqLn6/z8uYdH8Vmdnu9vz4WwXgN/xTPRD2+sgOcZjsge/whXZgUaYYvT8QnuJaUrjrHUQreGczuEafQCO/SJTufTbroWsPgsllVhq3wJEk2jUSzX3CUEDJC84707djRc5MTAQxoLgupWRwW6UB5fS++NV8AbOZgnsC7BpEAAAAABJRU5ErkJggg==");background-position: 2px center;}.ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAJ0Uk5TAAB2k804AAAAPklEQVQY02NgIB68QuO3tiLznjAwpKTgNyDbMegwisCHZUETUZV0ZqOquBpXj2rtnpSJT1AEnnRmL2OgGgAAIKkRQap2htgAAAAASUVORK5CYII=");background-position: 2px center;}.ace_dark .ace_gutter-cell.ace_info {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAJFBMVEUAAAChoaGAgIAqKiq+vr6tra1ZWVmUlJSbm5s8PDxubm56enrdgzg3AAAAAXRSTlMAQObYZgAAAClJREFUeNpjYMAPdsMYHegyJZFQBlsUlMFVCWUYKkAZMxZAGdxlDMQBAG+TBP4B6RyJAAAAAElFTkSuQmCC");}.ace_scrollbar {contain: strict;position: absolute;right: 0;bottom: 0;z-index: 6;}.ace_scrollbar-inner {position: absolute;cursor: text;left: 0;top: 0;}.ace_scrollbar-v{overflow-x: hidden;overflow-y: scroll;top: 0;}.ace_scrollbar-h {overflow-x: scroll;overflow-y: hidden;left: 0;}.ace_print-margin {position: absolute;height: 100%;}.ace_text-input {position: absolute;z-index: 0;width: 0.5em;height: 1em;opacity: 0;background: transparent;-moz-appearance: none;appearance: none;border: none;resize: none;outline: none;overflow: hidden;font: inherit;padding: 0 1px;margin: 0 -1px;contain: strict;-ms-user-select: text;-moz-user-select: text;-webkit-user-select: text;user-select: text;white-space: pre!important;}.ace_text-input.ace_composition {background: transparent;color: inherit;z-index: 1000;opacity: 1;}.ace_composition_placeholder { color: transparent }.ace_composition_marker { border-bottom: 1px solid;position: absolute;border-radius: 0;margin-top: 1px;}[ace_nocontext=true] {transform: none!important;filter: none!important;clip-path: none!important;mask : none!important;contain: none!important;perspective: none!important;mix-blend-mode: initial!important;z-index: auto;}.ace_layer {z-index: 1;position: absolute;overflow: hidden;word-wrap: normal;white-space: pre;height: 100%;width: 100%;box-sizing: border-box;pointer-events: none;}.ace_gutter-layer {position: relative;width: auto;text-align: right;pointer-events: auto;height: 1000000px;contain: style size layout;}.ace_text-layer {font: inherit !important;position: absolute;height: 1000000px;width: 1000000px;contain: style size layout;}.ace_text-layer > .ace_line, .ace_text-layer > .ace_line_group {contain: style size layout;position: absolute;top: 0;left: 0;right: 0;}.ace_hidpi .ace_text-layer,.ace_hidpi .ace_gutter-layer,.ace_hidpi .ace_content,.ace_hidpi .ace_gutter {contain: strict;will-change: transform;}.ace_hidpi .ace_text-layer > .ace_line, .ace_hidpi .ace_text-layer > .ace_line_group {contain: strict;}.ace_cjk {display: inline-block;text-align: center;}.ace_cursor-layer {z-index: 4;}.ace_cursor {z-index: 4;position: absolute;box-sizing: border-box;border-left: 2px solid;transform: translatez(0);}.ace_multiselect .ace_cursor {border-left-width: 1px;}.ace_slim-cursors .ace_cursor {border-left-width: 1px;}.ace_overwrite-cursors .ace_cursor {border-left-width: 0;border-bottom: 1px solid;}.ace_hidden-cursors .ace_cursor {opacity: 0.2;}.ace_hasPlaceholder .ace_hidden-cursors .ace_cursor {opacity: 0;}.ace_smooth-blinking .ace_cursor {transition: opacity 0.18s;}.ace_animate-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: step-end;animation-name: blink-ace-animate;animation-iteration-count: infinite;}.ace_animate-blinking.ace_smooth-blinking .ace_cursor {animation-duration: 1000ms;animation-timing-function: ease-in-out;animation-name: blink-ace-animate-smooth;}@keyframes blink-ace-animate {from, to { opacity: 1; }60% { opacity: 0; }}@keyframes blink-ace-animate-smooth {from, to { opacity: 1; }45% { opacity: 1; }60% { opacity: 0; }85% { opacity: 0; }}.ace_marker-layer .ace_step, .ace_marker-layer .ace_stack {position: absolute;z-index: 3;}.ace_marker-layer .ace_selection {position: absolute;z-index: 5;}.ace_marker-layer .ace_bracket {position: absolute;z-index: 6;}.ace_marker-layer .ace_error_bracket {position: absolute;border-bottom: 1px solid #DE5555;border-radius: 0;}.ace_marker-layer .ace_active-line {position: absolute;z-index: 2;}.ace_marker-layer .ace_selected-word {position: absolute;z-index: 4;box-sizing: border-box;}.ace_line .ace_fold {box-sizing: border-box;display: inline-block;height: 11px;margin-top: -2px;vertical-align: middle;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACJJREFUeNpi+P//fxgTAwPDBxDxD078RSX+YeEyDFMCIMAAI3INmXiwf2YAAAAASUVORK5CYII=");background-repeat: no-repeat, repeat-x;background-position: center center, top left;color: transparent;border: 1px solid black;border-radius: 2px;cursor: pointer;pointer-events: auto;}.ace_dark .ace_fold {}.ace_fold:hover{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAJCAYAAADU6McMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJpJREFUeNpi/P//PwOlgAXGYGRklAVSokD8GmjwY1wasKljQpYACtpCFeADcHVQfQyMQAwzwAZI3wJKvCLkfKBaMSClBlR7BOQikCFGQEErIH0VqkabiGCAqwUadAzZJRxQr/0gwiXIal8zQQPnNVTgJ1TdawL0T5gBIP1MUJNhBv2HKoQHHjqNrA4WO4zY0glyNKLT2KIfIMAAQsdgGiXvgnYAAAAASUVORK5CYII="),url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAA3CAYAAADNNiA5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAACBJREFUeNpi+P//fz4TAwPDZxDxD5X4i5fLMEwJgAADAEPVDbjNw87ZAAAAAElFTkSuQmCC");}.ace_tooltip {background-color: #FFF;background-image: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1));border: 1px solid gray;border-radius: 1px;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);color: black;max-width: 100%;padding: 3px 4px;position: fixed;z-index: 999999;box-sizing: border-box;cursor: default;white-space: pre;word-wrap: break-word;line-height: normal;font-style: normal;font-weight: normal;letter-spacing: normal;pointer-events: none;}.ace_folding-enabled > .ace_gutter-cell {padding-right: 13px;}.ace_fold-widget {box-sizing: border-box;margin: 0 -12px 0 1px;display: none;width: 11px;vertical-align: top;background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42mWKsQ0AMAzC8ixLlrzQjzmBiEjp0A6WwBCSPgKAXoLkqSot7nN3yMwR7pZ32NzpKkVoDBUxKAAAAABJRU5ErkJggg==");background-repeat: no-repeat;background-position: center;border-radius: 3px;border: 1px solid transparent;cursor: pointer;}.ace_folding-enabled .ace_fold-widget {display: inline-block; }.ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAANElEQVR42m3HwQkAMAhD0YzsRchFKI7sAikeWkrxwScEB0nh5e7KTPWimZki4tYfVbX+MNl4pyZXejUO1QAAAABJRU5ErkJggg==");}.ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAGCAYAAAAG5SQMAAAAOUlEQVR42jXKwQkAMAgDwKwqKD4EwQ26sSOkVWjgIIHAzPiCgaqiqnJHZnKICBERHN194O5b9vbLuAVRL+l0YWnZAAAAAElFTkSuQmCCXA==");}.ace_fold-widget:hover {border: 1px solid rgba(0, 0, 0, 0.3);background-color: rgba(255, 255, 255, 0.2);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.7);}.ace_fold-widget:active {border: 1px solid rgba(0, 0, 0, 0.4);background-color: rgba(0, 0, 0, 0.05);box-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);}.ace_dark .ace_fold-widget {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHklEQVQIW2P4//8/AzoGEQ7oGCaLLAhWiSwB146BAQCSTPYocqT0AAAAAElFTkSuQmCC");}.ace_dark .ace_fold-widget.ace_end {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH0lEQVQIW2P4//8/AxQ7wNjIAjDMgC4AxjCVKBirIAAF0kz2rlhxpAAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget.ace_closed {background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAHElEQVQIW2P4//+/AxAzgDADlOOAznHAKgPWAwARji8UIDTfQQAAAABJRU5ErkJggg==");}.ace_dark .ace_fold-widget:hover {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);background-color: rgba(255, 255, 255, 0.1);}.ace_dark .ace_fold-widget:active {box-shadow: 0 1px 1px rgba(255, 255, 255, 0.2);}.ace_inline_button {border: 1px solid lightgray;display: inline-block;margin: -1px 8px;padding: 0 5px;pointer-events: auto;cursor: pointer;}.ace_inline_button:hover {border-color: gray;background: rgba(200,200,200,0.2);display: inline-block;pointer-events: auto;}.ace_fold-widget.ace_invalid {background-color: #FFB4B4;border-color: #DE5555;}.ace_fade-fold-widgets .ace_fold-widget {transition: opacity 0.4s ease 0.05s;opacity: 0;}.ace_fade-fold-widgets:hover .ace_fold-widget {transition: opacity 0.05s ease 0.05s;opacity:1;}.ace_underline {text-decoration: underline;}.ace_bold {font-weight: bold;}.ace_nobold .ace_bold {font-weight: normal;}.ace_italic {font-style: italic;}.ace_error-marker {background-color: rgba(255, 0, 0,0.2);position: absolute;z-index: 9;}.ace_highlight-marker {background-color: rgba(255, 255, 0,0.2);position: absolute;z-index: 8;}.ace_mobile-menu {position: absolute;line-height: 1.5;border-radius: 4px;-ms-user-select: none;-moz-user-select: none;-webkit-user-select: none;user-select: none;background: white;box-shadow: 1px 3px 2px grey;border: 1px solid #dcdcdc;color: black;}.ace_dark > .ace_mobile-menu {background: #333;color: #ccc;box-shadow: 1px 3px 2px grey;border: 1px solid #444;}.ace_mobile-button {padding: 2px;cursor: pointer;overflow: hidden;}.ace_mobile-button:hover {background-color: #eee;opacity:1;}.ace_mobile-button:active {background-color: #ddd;}.ace_placeholder {font-family: arial;transform: scale(0.9);transform-origin: left;white-space: pre;opacity: 0.7;margin: 0 10px;}', m = e("./lib/useragent"), g = m.isIE; i.importCssString(v, "ace_editor.css"); var y = function (e, t) { var n = this; this.container = e || i.createElement("div"), i.addCssClass(this.container, "ace_editor"), i.HI_DPI && i.addCssClass(this.container, "ace_hidpi"), this.setTheme(t), this.$gutter = i.createElement("div"), this.$gutter.className = "ace_gutter", this.container.appendChild(this.$gutter), this.$gutter.setAttribute("aria-hidden", !0), this.scroller = i.createElement("div"), this.scroller.className = "ace_scroller", this.container.appendChild(this.scroller), this.content = i.createElement("div"), this.content.className = "ace_content", this.scroller.appendChild(this.content), this.$gutterLayer = new o(this.$gutter), this.$gutterLayer.on("changeGutterWidth", this.onGutterResize.bind(this)), this.$markerBack = new u(this.content); var r = this.$textLayer = new a(this.content); this.canvas = r.element, this.$markerFront = new u(this.content), this.$cursorLayer = new f(this.content), this.$horizScroll = !1, this.$vScroll = !1, this.scrollBar = this.scrollBarV = new c(this.container, this), this.scrollBarH = new l(this.container, this), this.scrollBarV.on("scroll", function (e) { n.$scrollAnimation || n.session.setScrollTop(e.data - n.scrollMargin.top) }), this.scrollBarH.on("scroll", function (e) { n.$scrollAnimation || n.session.setScrollLeft(e.data - n.scrollMargin.left) }), this.scrollTop = 0, this.scrollLeft = 0, this.cursorPos = { row: 0, column: 0 }, this.$fontMetrics = new p(this.container), this.$textLayer.$setFontMetrics(this.$fontMetrics), this.$textLayer.on("changeCharacterSize", function (e) { n.updateCharacterSize(), n.onResize(!0, n.gutterWidth, n.$size.width, n.$size.height), n._signal("changeCharacterSize", e) }), this.$size = { width: 0, height: 0, scrollerHeight: 0, scrollerWidth: 0, $dirty: !0 }, this.layerConfig = { width: 1, padding: 0, firstRow: 0, firstRowScreen: 0, lastRow: 0, lineHeight: 0, characterWidth: 0, minHeight: 1, maxHeight: 1, offset: 0, height: 1, gutterOffset: 1 }, this.scrollMargin = { left: 0, right: 0, top: 0, bottom: 0, v: 0, h: 0 }, this.margin = { left: 0, right: 0, top: 0, bottom: 0, v: 0, h: 0 }, this.$keepTextAreaAtCursor = !m.isIOS, this.$loop = new h(this.$renderChanges.bind(this), this.container.ownerDocument.defaultView), this.$loop.schedule(this.CHANGE_FULL), this.updateCharacterSize(), this.setPadding(4), s.resetOptions(this), s._signal("renderer", this) }; (function () { this.CHANGE_CURSOR = 1, this.CHANGE_MARKER = 2, this.CHANGE_GUTTER = 4, this.CHANGE_SCROLL = 8, this.CHANGE_LINES = 16, this.CHANGE_TEXT = 32, this.CHANGE_SIZE = 64, this.CHANGE_MARKER_BACK = 128, this.CHANGE_MARKER_FRONT = 256, this.CHANGE_FULL = 512, this.CHANGE_H_SCROLL = 1024, r.implement(this, d), this.updateCharacterSize = function () { this.$textLayer.allowBoldFonts != this.$allowBoldFonts && (this.$allowBoldFonts = this.$textLayer.allowBoldFonts, this.setStyle("ace_nobold", !this.$allowBoldFonts)), this.layerConfig.characterWidth = this.characterWidth = this.$textLayer.getCharacterWidth(), this.layerConfig.lineHeight = this.lineHeight = this.$textLayer.getLineHeight(), this.$updatePrintMargin(), i.setStyle(this.scroller.style, "line-height", this.lineHeight + "px") }, this.setSession = function (e) { this.session && this.session.doc.off("changeNewLineMode", this.onChangeNewLineMode), this.session = e, e && this.scrollMargin.top && e.getScrollTop() <= 0 && e.setScrollTop(-this.scrollMargin.top), this.$cursorLayer.setSession(e), this.$markerBack.setSession(e), this.$markerFront.setSession(e), this.$gutterLayer.setSession(e), this.$textLayer.setSession(e); if (!e) return; this.$loop.schedule(this.CHANGE_FULL), this.session.$setFontMetrics(this.$fontMetrics), this.scrollBarH.scrollLeft = this.scrollBarV.scrollTop = null, this.onChangeNewLineMode = this.onChangeNewLineMode.bind(this), this.onChangeNewLineMode(), this.session.doc.on("changeNewLineMode", this.onChangeNewLineMode) }, this.updateLines = function (e, t, n) { t === undefined && (t = Infinity), this.$changedLines ? (this.$changedLines.firstRow > e && (this.$changedLines.firstRow = e), this.$changedLines.lastRow < t && (this.$changedLines.lastRow = t)) : this.$changedLines = { firstRow: e, lastRow: t }; if (this.$changedLines.lastRow < this.layerConfig.firstRow) { if (!n) return; this.$changedLines.lastRow = this.layerConfig.lastRow } if (this.$changedLines.firstRow > this.layerConfig.lastRow) return; this.$loop.schedule(this.CHANGE_LINES) }, this.onChangeNewLineMode = function () { this.$loop.schedule(this.CHANGE_TEXT), this.$textLayer.$updateEolChar(), this.session.$bidiHandler.setEolChar(this.$textLayer.EOL_CHAR) }, this.onChangeTabSize = function () { this.$loop.schedule(this.CHANGE_TEXT | this.CHANGE_MARKER), this.$textLayer.onChangeTabSize() }, this.updateText = function () { this.$loop.schedule(this.CHANGE_TEXT) }, this.updateFull = function (e) { e ? this.$renderChanges(this.CHANGE_FULL, !0) : this.$loop.schedule(this.CHANGE_FULL) }, this.updateFontSize = function () { this.$textLayer.checkForSizeChanges() }, this.$changes = 0, this.$updateSizeAsync = function () { this.$loop.pending ? this.$size.$dirty = !0 : this.onResize() }, this.onResize = function (e, t, n, r) { if (this.resizing > 2) return; this.resizing > 0 ? this.resizing++ : this.resizing = e ? 1 : 0; var i = this.container; r || (r = i.clientHeight || i.scrollHeight), n || (n = i.clientWidth || i.scrollWidth); var s = this.$updateCachedSize(e, t, n, r); if (!this.$size.scrollerHeight || !n && !r) return this.resizing = 0; e && (this.$gutterLayer.$padding = null), e ? this.$renderChanges(s | this.$changes, !0) : this.$loop.schedule(s | this.$changes), this.resizing && (this.resizing = 0), this.scrollBarV.scrollLeft = this.scrollBarV.scrollTop = null }, this.$updateCachedSize = function (e, t, n, r) { r -= this.$extraHeight || 0; var s = 0, o = this.$size, u = { width: o.width, height: o.height, scrollerHeight: o.scrollerHeight, scrollerWidth: o.scrollerWidth }; r && (e || o.height != r) && (o.height = r, s |= this.CHANGE_SIZE, o.scrollerHeight = o.height, this.$horizScroll && (o.scrollerHeight -= this.scrollBarH.getHeight()), this.scrollBarV.element.style.bottom = this.scrollBarH.getHeight() + "px", s |= this.CHANGE_SCROLL); if (n && (e || o.width != n)) { s |= this.CHANGE_SIZE, o.width = n, t == null && (t = this.$showGutter ? this.$gutter.offsetWidth : 0), this.gutterWidth = t, i.setStyle(this.scrollBarH.element.style, "left", t + "px"), i.setStyle(this.scroller.style, "left", t + this.margin.left + "px"), o.scrollerWidth = Math.max(0, n - t - this.scrollBarV.getWidth() - this.margin.h), i.setStyle(this.$gutter.style, "left", this.margin.left + "px"); var a = this.scrollBarV.getWidth() + "px"; i.setStyle(this.scrollBarH.element.style, "right", a), i.setStyle(this.scroller.style, "right", a), i.setStyle(this.scroller.style, "bottom", this.scrollBarH.getHeight()); if (this.session && this.session.getUseWrapMode() && this.adjustWrapLimit() || e) s |= this.CHANGE_FULL } return o.$dirty = !n || !r, s && this._signal("resize", u), s }, this.onGutterResize = function (e) { var t = this.$showGutter ? e : 0; t != this.gutterWidth && (this.$changes |= this.$updateCachedSize(!0, t, this.$size.width, this.$size.height)), this.session.getUseWrapMode() && this.adjustWrapLimit() ? this.$loop.schedule(this.CHANGE_FULL) : this.$size.$dirty ? this.$loop.schedule(this.CHANGE_FULL) : this.$computeLayerConfig() }, this.adjustWrapLimit = function () { var e = this.$size.scrollerWidth - this.$padding * 2, t = Math.floor(e / this.characterWidth); return this.session.adjustWrapLimit(t, this.$showPrintMargin && this.$printMarginColumn) }, this.setAnimatedScroll = function (e) { this.setOption("animatedScroll", e) }, this.getAnimatedScroll = function () { return this.$animatedScroll }, this.setShowInvisibles = function (e) { this.setOption("showInvisibles", e), this.session.$bidiHandler.setShowInvisibles(e) }, this.getShowInvisibles = function () { return this.getOption("showInvisibles") }, this.getDisplayIndentGuides = function () { return this.getOption("displayIndentGuides") }, this.setDisplayIndentGuides = function (e) { this.setOption("displayIndentGuides", e) }, this.setShowPrintMargin = function (e) { this.setOption("showPrintMargin", e) }, this.getShowPrintMargin = function () { return this.getOption("showPrintMargin") }, this.setPrintMarginColumn = function (e) { this.setOption("printMarginColumn", e) }, this.getPrintMarginColumn = function () { return this.getOption("printMarginColumn") }, this.getShowGutter = function () { return this.getOption("showGutter") }, this.setShowGutter = function (e) { return this.setOption("showGutter", e) }, this.getFadeFoldWidgets = function () { return this.getOption("fadeFoldWidgets") }, this.setFadeFoldWidgets = function (e) { this.setOption("fadeFoldWidgets", e) }, this.setHighlightGutterLine = function (e) { this.setOption("highlightGutterLine", e) }, this.getHighlightGutterLine = function () { return this.getOption("highlightGutterLine") }, this.$updatePrintMargin = function () { if (!this.$showPrintMargin && !this.$printMarginEl) return; if (!this.$printMarginEl) { var e = i.createElement("div"); e.className = "ace_layer ace_print-margin-layer", this.$printMarginEl = i.createElement("div"), this.$printMarginEl.className = "ace_print-margin", e.appendChild(this.$printMarginEl), this.content.insertBefore(e, this.content.firstChild) } var t = this.$printMarginEl.style; t.left = Math.round(this.characterWidth * this.$printMarginColumn + this.$padding) + "px", t.visibility = this.$showPrintMargin ? "visible" : "hidden", this.session && this.session.$wrap == -1 && this.adjustWrapLimit() }, this.getContainerElement = function () { return this.container }, this.getMouseEventTarget = function () { return this.scroller }, this.getTextAreaContainer = function () { return this.container }, this.$moveTextAreaToCursor = function () { if (this.$isMousePressed) return; var e = this.textarea.style, t = this.$composition; if (!this.$keepTextAreaAtCursor && !t) { i.translate(this.textarea, -100, 0); return } var n = this.$cursorLayer.$pixelPos; if (!n) return; t && t.markerRange && (n = this.$cursorLayer.getPixelPosition(t.markerRange.start, !0)); var r = this.layerConfig, s = n.top, o = n.left; s -= r.offset; var u = t && t.useTextareaForIME ? this.lineHeight : g ? 0 : 1; if (s < 0 || s > r.height - u) { i.translate(this.textarea, 0, 0); return } var a = 1, f = this.$size.height - u; if (!t) s += this.lineHeight; else if (t.useTextareaForIME) { var l = this.textarea.value; a = this.characterWidth * this.session.$getStringScreenWidth(l)[0] } else s += this.lineHeight + 2; o -= this.scrollLeft, o > this.$size.scrollerWidth - a && (o = this.$size.scrollerWidth - a), o += this.gutterWidth + this.margin.left, i.setStyle(e, "height", u + "px"), i.setStyle(e, "width", a + "px"), i.translate(this.textarea, Math.min(o, this.$size.scrollerWidth - a), Math.min(s, f)) }, this.getFirstVisibleRow = function () { return this.layerConfig.firstRow }, this.getFirstFullyVisibleRow = function () { return this.layerConfig.firstRow + (this.layerConfig.offset === 0 ? 0 : 1) }, this.getLastFullyVisibleRow = function () { var e = this.layerConfig, t = e.lastRow, n = this.session.documentToScreenRow(t, 0) * e.lineHeight; return n - this.session.getScrollTop() > e.height - e.lineHeight ? t - 1 : t }, this.getLastVisibleRow = function () { return this.layerConfig.lastRow }, this.$padding = null, this.setPadding = function (e) { this.$padding = e, this.$textLayer.setPadding(e), this.$cursorLayer.setPadding(e), this.$markerFront.setPadding(e), this.$markerBack.setPadding(e), this.$loop.schedule(this.CHANGE_FULL), this.$updatePrintMargin() }, this.setScrollMargin = function (e, t, n, r) { var i = this.scrollMargin; i.top = e | 0, i.bottom = t | 0, i.right = r | 0, i.left = n | 0, i.v = i.top + i.bottom, i.h = i.left + i.right, i.top && this.scrollTop <= 0 && this.session && this.session.setScrollTop(-i.top), this.updateFull() }, this.setMargin = function (e, t, n, r) { var i = this.margin; i.top = e | 0, i.bottom = t | 0, i.right = r | 0, i.left = n | 0, i.v = i.top + i.bottom, i.h = i.left + i.right, this.$updateCachedSize(!0, this.gutterWidth, this.$size.width, this.$size.height), this.updateFull() }, this.getHScrollBarAlwaysVisible = function () { return this.$hScrollBarAlwaysVisible }, this.setHScrollBarAlwaysVisible = function (e) { this.setOption("hScrollBarAlwaysVisible", e) }, this.getVScrollBarAlwaysVisible = function () { return this.$vScrollBarAlwaysVisible }, this.setVScrollBarAlwaysVisible = function (e) { this.setOption("vScrollBarAlwaysVisible", e) }, this.$updateScrollBarV = function () { var e = this.layerConfig.maxHeight, t = this.$size.scrollerHeight; !this.$maxLines && this.$scrollPastEnd && (e -= (t - this.lineHeight) * this.$scrollPastEnd, this.scrollTop > e - t && (e = this.scrollTop + t, this.scrollBarV.scrollTop = null)), this.scrollBarV.setScrollHeight(e + this.scrollMargin.v), this.scrollBarV.setScrollTop(this.scrollTop + this.scrollMargin.top) }, this.$updateScrollBarH = function () { this.scrollBarH.setScrollWidth(this.layerConfig.width + 2 * this.$padding + this.scrollMargin.h), this.scrollBarH.setScrollLeft(this.scrollLeft + this.scrollMargin.left) }, this.$frozen = !1, this.freeze = function () { this.$frozen = !0 }, this.unfreeze = function () { this.$frozen = !1 }, this.$renderChanges = function (e, t) { this.$changes && (e |= this.$changes, this.$changes = 0); if (!this.session || !this.container.offsetWidth || this.$frozen || !e && !t) { this.$changes |= e; return } if (this.$size.$dirty) return this.$changes |= e, this.onResize(!0); this.lineHeight || this.$textLayer.checkForSizeChanges(), this._signal("beforeRender", e), this.session && this.session.$bidiHandler && this.session.$bidiHandler.updateCharacterWidths(this.$fontMetrics); var n = this.layerConfig; if (e & this.CHANGE_FULL || e & this.CHANGE_SIZE || e & this.CHANGE_TEXT || e & this.CHANGE_LINES || e & this.CHANGE_SCROLL || e & this.CHANGE_H_SCROLL) { e |= this.$computeLayerConfig() | this.$loop.clear(); if (n.firstRow != this.layerConfig.firstRow && n.firstRowScreen == this.layerConfig.firstRowScreen) { var r = this.scrollTop + (n.firstRow - this.layerConfig.firstRow) * this.lineHeight; r > 0 && (this.scrollTop = r, e |= this.CHANGE_SCROLL, e |= this.$computeLayerConfig() | this.$loop.clear()) } n = this.layerConfig, this.$updateScrollBarV(), e & this.CHANGE_H_SCROLL && this.$updateScrollBarH(), i.translate(this.content, -this.scrollLeft, -n.offset); var s = n.width + 2 * this.$padding + "px", o = n.minHeight + "px"; i.setStyle(this.content.style, "width", s), i.setStyle(this.content.style, "height", o) } e & this.CHANGE_H_SCROLL && (i.translate(this.content, -this.scrollLeft, -n.offset), this.scroller.className = this.scrollLeft <= 0 ? "ace_scroller" : "ace_scroller ace_scroll-left"); if (e & this.CHANGE_FULL) { this.$changedLines = null, this.$textLayer.update(n), this.$showGutter && this.$gutterLayer.update(n), this.$markerBack.update(n), this.$markerFront.update(n), this.$cursorLayer.update(n), this.$moveTextAreaToCursor(), this._signal("afterRender", e); return } if (e & this.CHANGE_SCROLL) { this.$changedLines = null, e & this.CHANGE_TEXT || e & this.CHANGE_LINES ? this.$textLayer.update(n) : this.$textLayer.scrollLines(n), this.$showGutter && (e & this.CHANGE_GUTTER || e & this.CHANGE_LINES ? this.$gutterLayer.update(n) : this.$gutterLayer.scrollLines(n)), this.$markerBack.update(n), this.$markerFront.update(n), this.$cursorLayer.update(n), this.$moveTextAreaToCursor(), this._signal("afterRender", e); return } e & this.CHANGE_TEXT ? (this.$changedLines = null, this.$textLayer.update(n), this.$showGutter && this.$gutterLayer.update(n)) : e & this.CHANGE_LINES ? (this.$updateLines() || e & this.CHANGE_GUTTER && this.$showGutter) && this.$gutterLayer.update(n) : e & this.CHANGE_TEXT || e & this.CHANGE_GUTTER ? this.$showGutter && this.$gutterLayer.update(n) : e & this.CHANGE_CURSOR && this.$highlightGutterLine && this.$gutterLayer.updateLineHighlight(n), e & this.CHANGE_CURSOR && (this.$cursorLayer.update(n), this.$moveTextAreaToCursor()), e & (this.CHANGE_MARKER | this.CHANGE_MARKER_FRONT) && this.$markerFront.update(n), e & (this.CHANGE_MARKER | this.CHANGE_MARKER_BACK) && this.$markerBack.update(n), this._signal("afterRender", e) }, this.$autosize = function () { var e = this.session.getScreenLength() * this.lineHeight, t = this.$maxLines * this.lineHeight, n = Math.min(t, Math.max((this.$minLines || 1) * this.lineHeight, e)) + this.scrollMargin.v + (this.$extraHeight || 0); this.$horizScroll && (n += this.scrollBarH.getHeight()), this.$maxPixelHeight && n > this.$maxPixelHeight && (n = this.$maxPixelHeight); var r = n <= 2 * this.lineHeight, i = !r && e > t; if (n != this.desiredHeight || this.$size.height != this.desiredHeight || i != this.$vScroll) { i != this.$vScroll && (this.$vScroll = i, this.scrollBarV.setVisible(i)); var s = this.container.clientWidth; this.container.style.height = n + "px", this.$updateCachedSize(!0, this.$gutterWidth, s, n), this.desiredHeight = n, this._signal("autosize") } }, this.$computeLayerConfig = function () { var e = this.session, t = this.$size, n = t.height <= 2 * this.lineHeight, r = this.session.getScreenLength(), i = r * this.lineHeight, s = this.$getLongestLine(), o = !n && (this.$hScrollBarAlwaysVisible || t.scrollerWidth - s - 2 * this.$padding < 0), u = this.$horizScroll !== o; u && (this.$horizScroll = o, this.scrollBarH.setVisible(o)); var a = this.$vScroll; this.$maxLines && this.lineHeight > 1 && this.$autosize(); var f = t.scrollerHeight + this.lineHeight, l = !this.$maxLines && this.$scrollPastEnd ? (t.scrollerHeight - this.lineHeight) * this.$scrollPastEnd : 0; i += l; var c = this.scrollMargin; this.session.setScrollTop(Math.max(-c.top, Math.min(this.scrollTop, i - t.scrollerHeight + c.bottom))), this.session.setScrollLeft(Math.max(-c.left, Math.min(this.scrollLeft, s + 2 * this.$padding - t.scrollerWidth + c.right))); var h = !n && (this.$vScrollBarAlwaysVisible || t.scrollerHeight - i + l < 0 || this.scrollTop > c.top), p = a !== h; p && (this.$vScroll = h, this.scrollBarV.setVisible(h)); var d = this.scrollTop % this.lineHeight, v = Math.ceil(f / this.lineHeight) - 1, m = Math.max(0, Math.round((this.scrollTop - d) / this.lineHeight)), g = m + v, y, b, w = this.lineHeight; m = e.screenToDocumentRow(m, 0); var E = e.getFoldLine(m); E && (m = E.start.row), y = e.documentToScreenRow(m, 0), b = e.getRowLength(m) * w, g = Math.min(e.screenToDocumentRow(g, 0), e.getLength() - 1), f = t.scrollerHeight + e.getRowLength(g) * w + b, d = this.scrollTop - y * w; var S = 0; if (this.layerConfig.width != s || u) S = this.CHANGE_H_SCROLL; if (u || p) S |= this.$updateCachedSize(!0, this.gutterWidth, t.width, t.height), this._signal("scrollbarVisibilityChanged"), p && (s = this.$getLongestLine()); return this.layerConfig = { width: s, padding: this.$padding, firstRow: m, firstRowScreen: y, lastRow: g, lineHeight: w, characterWidth: this.characterWidth, minHeight: f, maxHeight: i, offset: d, gutterOffset: w ? Math.max(0, Math.ceil((d + t.height - t.scrollerHeight) / w)) : 0, height: this.$size.scrollerHeight }, this.session.$bidiHandler && this.session.$bidiHandler.setContentWidth(s - this.$padding), S }, this.$updateLines = function () { if (!this.$changedLines) return; var e = this.$changedLines.firstRow, t = this.$changedLines.lastRow; this.$changedLines = null; var n = this.layerConfig; if (e > n.lastRow + 1) return; if (t < n.firstRow) return; if (t === Infinity) { this.$showGutter && this.$gutterLayer.update(n), this.$textLayer.update(n); return } return this.$textLayer.updateLines(n, e, t), !0 }, this.$getLongestLine = function () { var e = this.session.getScreenWidth(); return this.showInvisibles && !this.session.$useWrapMode && (e += 1), this.$textLayer && e > this.$textLayer.MAX_LINE_LENGTH && (e = this.$textLayer.MAX_LINE_LENGTH + 30), Math.max(this.$size.scrollerWidth - 2 * this.$padding, Math.round(e * this.characterWidth)) }, this.updateFrontMarkers = function () { this.$markerFront.setMarkers(this.session.getMarkers(!0)), this.$loop.schedule(this.CHANGE_MARKER_FRONT) }, this.updateBackMarkers = function () { this.$markerBack.setMarkers(this.session.getMarkers()), this.$loop.schedule(this.CHANGE_MARKER_BACK) }, this.addGutterDecoration = function (e, t) { this.$gutterLayer.addGutterDecoration(e, t) }, this.removeGutterDecoration = function (e, t) { this.$gutterLayer.removeGutterDecoration(e, t) }, this.updateBreakpoints = function (e) { this.$loop.schedule(this.CHANGE_GUTTER) }, this.setAnnotations = function (e) { this.$gutterLayer.setAnnotations(e), this.$loop.schedule(this.CHANGE_GUTTER) }, this.updateCursor = function () { this.$loop.schedule(this.CHANGE_CURSOR) }, this.hideCursor = function () { this.$cursorLayer.hideCursor() }, this.showCursor = function () { this.$cursorLayer.showCursor() }, this.scrollSelectionIntoView = function (e, t, n) { this.scrollCursorIntoView(e, n), this.scrollCursorIntoView(t, n) }, this.scrollCursorIntoView = function (e, t, n) { if (this.$size.scrollerHeight === 0) return; var r = this.$cursorLayer.getPixelPosition(e), i = r.left, s = r.top, o = n && n.top || 0, u = n && n.bottom || 0, a = this.$scrollAnimation ? this.session.getScrollTop() : this.scrollTop; a + o > s ? (t && a + o > s + this.lineHeight && (s -= t * this.$size.scrollerHeight), s === 0 && (s = -this.scrollMargin.top), this.session.setScrollTop(s)) : a + this.$size.scrollerHeight - u < s + this.lineHeight && (t && a + this.$size.scrollerHeight - u < s - this.lineHeight && (s += t * this.$size.scrollerHeight), this.session.setScrollTop(s + this.lineHeight + u - this.$size.scrollerHeight)); var f = this.scrollLeft; f > i ? (i < this.$padding + 2 * this.layerConfig.characterWidth && (i = -this.scrollMargin.left), this.session.setScrollLeft(i)) : f + this.$size.scrollerWidth < i + this.characterWidth ? this.session.setScrollLeft(Math.round(i + this.characterWidth - this.$size.scrollerWidth)) : f <= this.$padding && i - f < this.characterWidth && this.session.setScrollLeft(0) }, this.getScrollTop = function () { return this.session.getScrollTop() }, this.getScrollLeft = function () { return this.session.getScrollLeft() }, this.getScrollTopRow = function () { return this.scrollTop / this.lineHeight }, this.getScrollBottomRow = function () { return Math.max(0, Math.floor((this.scrollTop + this.$size.scrollerHeight) / this.lineHeight) - 1) }, this.scrollToRow = function (e) { this.session.setScrollTop(e * this.lineHeight) }, this.alignCursor = function (e, t) { typeof e == "number" && (e = { row: e, column: 0 }); var n = this.$cursorLayer.getPixelPosition(e), r = this.$size.scrollerHeight - this.lineHeight, i = n.top - r * (t || 0); return this.session.setScrollTop(i), i }, this.STEPS = 8, this.$calcSteps = function (e, t) { var n = 0, r = this.STEPS, i = [], s = function (e, t, n) { return n * (Math.pow(e - 1, 3) + 1) + t }; for (n = 0; n < r; ++n)i.push(s(n / this.STEPS, e, t - e)); return i }, this.scrollToLine = function (e, t, n, r) { var i = this.$cursorLayer.getPixelPosition({ row: e, column: 0 }), s = i.top; t && (s -= this.$size.scrollerHeight / 2); var o = this.scrollTop; this.session.setScrollTop(s), n !== !1 && this.animateScrolling(o, r) }, this.animateScrolling = function (e, t) { var n = this.scrollTop; if (!this.$animatedScroll) return; var r = this; if (e == n) return; if (this.$scrollAnimation) { var i = this.$scrollAnimation.steps; if (i.length) { e = i[0]; if (e == n) return } } var s = r.$calcSteps(e, n); this.$scrollAnimation = { from: e, to: n, steps: s }, clearInterval(this.$timer), r.session.setScrollTop(s.shift()), r.session.$scrollTop = n, this.$timer = setInterval(function () { if (!r.session) return clearInterval(r.$timer); s.length ? (r.session.setScrollTop(s.shift()), r.session.$scrollTop = n) : n != null ? (r.session.$scrollTop = -1, r.session.setScrollTop(n), n = null) : (r.$timer = clearInterval(r.$timer), r.$scrollAnimation = null, t && t()) }, 10) }, this.scrollToY = function (e) { this.scrollTop !== e && (this.$loop.schedule(this.CHANGE_SCROLL), this.scrollTop = e) }, this.scrollToX = function (e) { this.scrollLeft !== e && (this.scrollLeft = e), this.$loop.schedule(this.CHANGE_H_SCROLL) }, this.scrollTo = function (e, t) { this.session.setScrollTop(t), this.session.setScrollLeft(t) }, this.scrollBy = function (e, t) { t && this.session.setScrollTop(this.session.getScrollTop() + t), e && this.session.setScrollLeft(this.session.getScrollLeft() + e) }, this.isScrollableBy = function (e, t) { if (t < 0 && this.session.getScrollTop() >= 1 - this.scrollMargin.top) return !0; if (t > 0 && this.session.getScrollTop() + this.$size.scrollerHeight - this.layerConfig.maxHeight < -1 + this.scrollMargin.bottom) return !0; if (e < 0 && this.session.getScrollLeft() >= 1 - this.scrollMargin.left) return !0; if (e > 0 && this.session.getScrollLeft() + this.$size.scrollerWidth - this.layerConfig.width < -1 + this.scrollMargin.right) return !0 }, this.pixelToScreenCoordinates = function (e, t) { var n; if (this.$hasCssTransforms) { n = { top: 0, left: 0 }; var r = this.$fontMetrics.transformCoordinates([e, t]); e = r[1] - this.gutterWidth - this.margin.left, t = r[0] } else n = this.scroller.getBoundingClientRect(); var i = e + this.scrollLeft - n.left - this.$padding, s = i / this.characterWidth, o = Math.floor((t + this.scrollTop - n.top) / this.lineHeight), u = this.$blockCursor ? Math.floor(s) : Math.round(s); return { row: o, column: u, side: s - u > 0 ? 1 : -1, offsetX: i } }, this.screenToTextCoordinates = function (e, t) { var n; if (this.$hasCssTransforms) { n = { top: 0, left: 0 }; var r = this.$fontMetrics.transformCoordinates([e, t]); e = r[1] - this.gutterWidth - this.margin.left, t = r[0] } else n = this.scroller.getBoundingClientRect(); var i = e + this.scrollLeft - n.left - this.$padding, s = i / this.characterWidth, o = this.$blockCursor ? Math.floor(s) : Math.round(s), u = Math.floor((t + this.scrollTop - n.top) / this.lineHeight); return this.session.screenToDocumentPosition(u, Math.max(o, 0), i) }, this.textToScreenCoordinates = function (e, t) { var n = this.scroller.getBoundingClientRect(), r = this.session.documentToScreenPosition(e, t), i = this.$padding + (this.session.$bidiHandler.isBidiRow(r.row, e) ? this.session.$bidiHandler.getPosLeft(r.column) : Math.round(r.column * this.characterWidth)), s = r.row * this.lineHeight; return { pageX: n.left + i - this.scrollLeft, pageY: n.top + s - this.scrollTop } }, this.visualizeFocus = function () { i.addCssClass(this.container, "ace_focus") }, this.visualizeBlur = function () { i.removeCssClass(this.container, "ace_focus") }, this.showComposition = function (e) { this.$composition = e, e.cssText || (e.cssText = this.textarea.style.cssText), e.useTextareaForIME == undefined && (e.useTextareaForIME = this.$useTextareaForIME), this.$useTextareaForIME ? (i.addCssClass(this.textarea, "ace_composition"), this.textarea.style.cssText = "", this.$moveTextAreaToCursor(), this.$cursorLayer.element.style.display = "none") : e.markerId = this.session.addMarker(e.markerRange, "ace_composition_marker", "text") }, this.setCompositionText = function (e) { var t = this.session.selection.cursor; this.addToken(e, "composition_placeholder", t.row, t.column), this.$moveTextAreaToCursor() }, this.hideComposition = function () { if (!this.$composition) return; this.$composition.markerId && this.session.removeMarker(this.$composition.markerId), i.removeCssClass(this.textarea, "ace_composition"), this.textarea.style.cssText = this.$composition.cssText; var e = this.session.selection.cursor; this.removeExtraToken(e.row, e.column), this.$composition = null, this.$cursorLayer.element.style.display = "" }, this.addToken = function (e, t, n, r) { var i = this.session; i.bgTokenizer.lines[n] = null; var s = { type: t, value: e }, o = i.getTokens(n); if (r == null) o.push(s); else { var u = 0; for (var a = 0; a < o.length; a++) { var f = o[a]; u += f.value.length; if (r <= u) { var l = f.value.length - (u - r), c = f.value.slice(0, l), h = f.value.slice(l); o.splice(a, 1, { type: f.type, value: c }, s, { type: f.type, value: h }); break } } } this.updateLines(n, n) }, this.removeExtraToken = function (e, t) { this.updateLines(e, e) }, this.setTheme = function (e, t) { function o(r) { if (n.$themeId != e) return t && t(); if (!r || !r.cssClass) throw new Error("couldn't load module " + e + " or it didn't call define"); r.$id && (n.$themeId = r.$id), i.importCssString(r.cssText, r.cssClass, n.container), n.theme && i.removeCssClass(n.container, n.theme.cssClass); var s = "padding" in r ? r.padding : "padding" in (n.theme || {}) ? 4 : n.$padding; n.$padding && s != n.$padding && n.setPadding(s), n.$theme = r.cssClass, n.theme = r, i.addCssClass(n.container, r.cssClass), i.setCssClass(n.container, "ace_dark", r.isDark), n.$size && (n.$size.width = 0, n.$updateSizeAsync()), n._dispatchEvent("themeLoaded", { theme: r }), t && t() } var n = this; this.$themeId = e, n._dispatchEvent("themeChange", { theme: e }); if (!e || typeof e == "string") { var r = e || this.$options.theme.initialValue; s.loadModule(["theme", r], o) } else o(e) }, this.getTheme = function () { return this.$themeId }, this.setStyle = function (e, t) { i.setCssClass(this.container, e, t !== !1) }, this.unsetStyle = function (e) { i.removeCssClass(this.container, e) }, this.setCursorStyle = function (e) { i.setStyle(this.scroller.style, "cursor", e) }, this.setMouseCursor = function (e) { i.setStyle(this.scroller.style, "cursor", e) }, this.attachToShadowRoot = function () { i.importCssString(v, "ace_editor.css", this.container) }, this.destroy = function () { this.freeze(), this.$fontMetrics.destroy(), this.$cursorLayer.destroy(), this.removeAllListeners(), this.container.textContent = "" } }).call(y.prototype), s.defineOptions(y.prototype, "renderer", { animatedScroll: { initialValue: !1 }, showInvisibles: { set: function (e) { this.$textLayer.setShowInvisibles(e) && this.$loop.schedule(this.CHANGE_TEXT) }, initialValue: !1 }, showPrintMargin: { set: function () { this.$updatePrintMargin() }, initialValue: !0 }, printMarginColumn: { set: function () { this.$updatePrintMargin() }, initialValue: 80 }, printMargin: { set: function (e) { typeof e == "number" && (this.$printMarginColumn = e), this.$showPrintMargin = !!e, this.$updatePrintMargin() }, get: function () { return this.$showPrintMargin && this.$printMarginColumn } }, showGutter: { set: function (e) { this.$gutter.style.display = e ? "block" : "none", this.$loop.schedule(this.CHANGE_FULL), this.onGutterResize() }, initialValue: !0 }, fadeFoldWidgets: { set: function (e) { i.setCssClass(this.$gutter, "ace_fade-fold-widgets", e) }, initialValue: !1 }, showFoldWidgets: { set: function (e) { this.$gutterLayer.setShowFoldWidgets(e), this.$loop.schedule(this.CHANGE_GUTTER) }, initialValue: !0 }, displayIndentGuides: { set: function (e) { this.$textLayer.setDisplayIndentGuides(e) && this.$loop.schedule(this.CHANGE_TEXT) }, initialValue: !0 }, highlightGutterLine: { set: function (e) { this.$gutterLayer.setHighlightGutterLine(e), this.$loop.schedule(this.CHANGE_GUTTER) }, initialValue: !0 }, hScrollBarAlwaysVisible: { set: function (e) { (!this.$hScrollBarAlwaysVisible || !this.$horizScroll) && this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: !1 }, vScrollBarAlwaysVisible: { set: function (e) { (!this.$vScrollBarAlwaysVisible || !this.$vScroll) && this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: !1 }, fontSize: { set: function (e) { typeof e == "number" && (e += "px"), this.container.style.fontSize = e, this.updateFontSize() }, initialValue: 12 }, fontFamily: { set: function (e) { this.container.style.fontFamily = e, this.updateFontSize() } }, maxLines: { set: function (e) { this.updateFull() } }, minLines: { set: function (e) { this.$minLines < 562949953421311 || (this.$minLines = 0), this.updateFull() } }, maxPixelHeight: { set: function (e) { this.updateFull() }, initialValue: 0 }, scrollPastEnd: { set: function (e) { e = +e || 0; if (this.$scrollPastEnd == e) return; this.$scrollPastEnd = e, this.$loop.schedule(this.CHANGE_SCROLL) }, initialValue: 0, handlesSet: !0 }, fixedWidthGutter: { set: function (e) { this.$gutterLayer.$fixedWidth = !!e, this.$loop.schedule(this.CHANGE_GUTTER) } }, theme: { set: function (e) { this.setTheme(e) }, get: function () { return this.$themeId || this.theme }, initialValue: "./theme/textmate", handlesSet: !0 }, hasCssTransforms: {}, useTextareaForIME: { initialValue: !m.isMobile && !m.isIE } }), t.VirtualRenderer = y }), define("ace/worker/worker_client", ["require", "exports", "module", "ace/lib/oop", "ace/lib/net", "ace/lib/event_emitter", "ace/config"], function (e, t, n) { "use strict"; function u(e) { var t = "importScripts('" + i.qualifyURL(e) + "');"; try { return new Blob([t], { type: "application/javascript" }) } catch (n) { var r = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder, s = new r; return s.append(t), s.getBlob("application/javascript") } } function a(e) { if (typeof Worker == "undefined") return { postMessage: function () { }, terminate: function () { } }; if (o.get("loadWorkerFromBlob")) { var t = u(e), n = window.URL || window.webkitURL, r = n.createObjectURL(t); return new Worker(r) } return new Worker(e) } var r = e("../lib/oop"), i = e("../lib/net"), s = e("../lib/event_emitter").EventEmitter, o = e("../config"), f = function (e) { e.postMessage || (e = this.$createWorkerFromOldConfig.apply(this, arguments)), this.$worker = e, this.$sendDeltaQueue = this.$sendDeltaQueue.bind(this), this.changeListener = this.changeListener.bind(this), this.onMessage = this.onMessage.bind(this), this.callbackId = 1, this.callbacks = {}, this.$worker.onmessage = this.onMessage }; (function () { r.implement(this, s), this.$createWorkerFromOldConfig = function (t, n, r, i, s) { e.nameToUrl && !e.toUrl && (e.toUrl = e.nameToUrl); if (o.get("packaged") || !e.toUrl) i = i || o.moduleUrl(n, "worker"); else { var u = this.$normalizePath; i = i || u(e.toUrl("ace/worker/worker.js", null, "_")); var f = {}; t.forEach(function (t) { f[t] = u(e.toUrl(t, null, "_").replace(/(\.js)?(\?.*)?$/, "")) }) } return this.$worker = a(i), s && this.send("importScripts", s), this.$worker.postMessage({ init: !0, tlns: f, module: n, classname: r }), this.$worker }, this.onMessage = function (e) { var t = e.data; switch (t.type) { case "event": this._signal(t.name, { data: t.data }); break; case "call": var n = this.callbacks[t.id]; n && (n(t.data), delete this.callbacks[t.id]); break; case "error": this.reportError(t.data); break; case "log": window.console && console.log && console.log.apply(console, t.data) } }, this.reportError = function (e) { window.console && console.error && console.error(e) }, this.$normalizePath = function (e) { return i.qualifyURL(e) }, this.terminate = function () { this._signal("terminate", {}), this.deltaQueue = null, this.$worker.terminate(), this.$worker = null, this.$doc && this.$doc.off("change", this.changeListener), this.$doc = null }, this.send = function (e, t) { this.$worker.postMessage({ command: e, args: t }) }, this.call = function (e, t, n) { if (n) { var r = this.callbackId++; this.callbacks[r] = n, t.push(r) } this.send(e, t) }, this.emit = function (e, t) { try { t.data && t.data.err && (t.data.err = { message: t.data.err.message, stack: t.data.err.stack, code: t.data.err.code }), this.$worker.postMessage({ event: e, data: { data: t.data } }) } catch (n) { console.error(n.stack) } }, this.attachToDocument = function (e) { this.$doc && this.terminate(), this.$doc = e, this.call("setValue", [e.getValue()]), e.on("change", this.changeListener) }, this.changeListener = function (e) { this.deltaQueue || (this.deltaQueue = [], setTimeout(this.$sendDeltaQueue, 0)), e.action == "insert" ? this.deltaQueue.push(e.start, e.lines) : this.deltaQueue.push(e.start, e.end) }, this.$sendDeltaQueue = function () { var e = this.deltaQueue; if (!e) return; this.deltaQueue = null, e.length > 50 && e.length > this.$doc.getLength() >> 1 ? this.call("setValue", [this.$doc.getValue()]) : this.emit("change", { data: e }) } }).call(f.prototype); var l = function (e, t, n) { var r = null, i = !1, u = Object.create(s), a = [], l = new f({ messageBuffer: a, terminate: function () { }, postMessage: function (e) { a.push(e); if (!r) return; i ? setTimeout(c) : c() } }); l.setEmitSync = function (e) { i = e }; var c = function () { var e = a.shift(); e.command ? r[e.command].apply(r, e.args) : e.event && u._signal(e.event, e.data) }; return u.postMessage = function (e) { l.onMessage({ data: e }) }, u.callback = function (e, t) { this.postMessage({ type: "call", id: t, data: e }) }, u.emit = function (e, t) { this.postMessage({ type: "event", name: e, data: t }) }, o.loadModule(["worker", t], function (e) { r = new e[n](u); while (a.length) c() }), l }; t.UIWorkerClient = l, t.WorkerClient = f, t.createWorker = a }), define("ace/placeholder", ["require", "exports", "module", "ace/range", "ace/lib/event_emitter", "ace/lib/oop"], function (e, t, n) { "use strict"; var r = e("./range").Range, i = e("./lib/event_emitter").EventEmitter, s = e("./lib/oop"), o = function (e, t, n, r, i, s) { var o = this; this.length = t, this.session = e, this.doc = e.getDocument(), this.mainClass = i, this.othersClass = s, this.$onUpdate = this.onUpdate.bind(this), this.doc.on("change", this.$onUpdate), this.$others = r, this.$onCursorChange = function () { setTimeout(function () { o.onCursorChange() }) }, this.$pos = n; var u = e.getUndoManager().$undoStack || e.getUndoManager().$undostack || { length: -1 }; this.$undoStackDepth = u.length, this.setup(), e.selection.on("changeCursor", this.$onCursorChange) }; (function () { s.implement(this, i), this.setup = function () { var e = this, t = this.doc, n = this.session; this.selectionBefore = n.selection.toJSON(), n.selection.inMultiSelectMode && n.selection.toSingleRange(), this.pos = t.createAnchor(this.$pos.row, this.$pos.column); var i = this.pos; i.$insertRight = !0, i.detach(), i.markerId = n.addMarker(new r(i.row, i.column, i.row, i.column + this.length), this.mainClass, null, !1), this.others = [], this.$others.forEach(function (n) { var r = t.createAnchor(n.row, n.column); r.$insertRight = !0, r.detach(), e.others.push(r) }), n.setUndoSelect(!1) }, this.showOtherMarkers = function () { if (this.othersActive) return; var e = this.session, t = this; this.othersActive = !0, this.others.forEach(function (n) { n.markerId = e.addMarker(new r(n.row, n.column, n.row, n.column + t.length), t.othersClass, null, !1) }) }, this.hideOtherMarkers = function () { if (!this.othersActive) return; this.othersActive = !1; for (var e = 0; e < this.others.length; e++)this.session.removeMarker(this.others[e].markerId) }, this.onUpdate = function (e) { if (this.$updating) return this.updateAnchors(e); var t = e; if (t.start.row !== t.end.row) return; if (t.start.row !== this.pos.row) return; this.$updating = !0; var n = e.action === "insert" ? t.end.column - t.start.column : t.start.column - t.end.column, i = t.start.column >= this.pos.column && t.start.column <= this.pos.column + this.length + 1, s = t.start.column - this.pos.column; this.updateAnchors(e), i && (this.length += n); if (i && !this.session.$fromUndo) if (e.action === "insert") for (var o = this.others.length - 1; o >= 0; o--) { var u = this.others[o], a = { row: u.row, column: u.column + s }; this.doc.insertMergedLines(a, e.lines) } else if (e.action === "remove") for (var o = this.others.length - 1; o >= 0; o--) { var u = this.others[o], a = { row: u.row, column: u.column + s }; this.doc.remove(new r(a.row, a.column, a.row, a.column - n)) } this.$updating = !1, this.updateMarkers() }, this.updateAnchors = function (e) { this.pos.onChange(e); for (var t = this.others.length; t--;)this.others[t].onChange(e); this.updateMarkers() }, this.updateMarkers = function () { if (this.$updating) return; var e = this, t = this.session, n = function (n, i) { t.removeMarker(n.markerId), n.markerId = t.addMarker(new r(n.row, n.column, n.row, n.column + e.length), i, null, !1) }; n(this.pos, this.mainClass); for (var i = this.others.length; i--;)n(this.others[i], this.othersClass) }, this.onCursorChange = function (e) { if (this.$updating || !this.session) return; var t = this.session.selection.getCursor(); t.row === this.pos.row && t.column >= this.pos.column && t.column <= this.pos.column + this.length ? (this.showOtherMarkers(), this._emit("cursorEnter", e)) : (this.hideOtherMarkers(), this._emit("cursorLeave", e)) }, this.detach = function () { this.session.removeMarker(this.pos && this.pos.markerId), this.hideOtherMarkers(), this.doc.off("change", this.$onUpdate), this.session.selection.off("changeCursor", this.$onCursorChange), this.session.setUndoSelect(!0), this.session = null }, this.cancel = function () { if (this.$undoStackDepth === -1) return; var e = this.session.getUndoManager(), t = (e.$undoStack || e.$undostack).length - this.$undoStackDepth; for (var n = 0; n < t; n++)e.undo(this.session, !0); this.selectionBefore && this.session.selection.fromJSON(this.selectionBefore) } }).call(o.prototype), t.PlaceHolder = o }), define("ace/mouse/multi_select_handler", ["require", "exports", "module", "ace/lib/event", "ace/lib/useragent"], function (e, t, n) { function s(e, t) { return e.row == t.row && e.column == t.column } function o(e) { var t = e.domEvent, n = t.altKey, o = t.shiftKey, u = t.ctrlKey, a = e.getAccelKey(), f = e.getButton(); u && i.isMac && (f = t.button); if (e.editor.inMultiSelectMode && f == 2) { e.editor.textInput.onContextMenu(e.domEvent); return } if (!u && !n && !a) { f === 0 && e.editor.inMultiSelectMode && e.editor.exitMultiSelectMode(); return } if (f !== 0) return; var l = e.editor, c = l.selection, h = l.inMultiSelectMode, p = e.getDocumentPosition(), d = c.getCursor(), v = e.inSelection() || c.isEmpty() && s(p, d), m = e.x, g = e.y, y = function (e) { m = e.clientX, g = e.clientY }, b = l.session, w = l.renderer.pixelToScreenCoordinates(m, g), E = w, S; if (l.$mouseHandler.$enableJumpToDef) u && n || a && n ? S = o ? "block" : "add" : n && l.$blockSelectEnabled && (S = "block"); else if (a && !n) { S = "add"; if (!h && o) return } else n && l.$blockSelectEnabled && (S = "block"); S && i.isMac && t.ctrlKey && l.$mouseHandler.cancelContextMenu(); if (S == "add") { if (!h && v) return; if (!h) { var x = c.toOrientedRange(); l.addSelectionMarker(x) } var T = c.rangeList.rangeAtPoint(p); l.inVirtualSelectionMode = !0, o && (T = null, x = c.ranges[0] || x, l.removeSelectionMarker(x)), l.once("mouseup", function () { var e = c.toOrientedRange(); T && e.isEmpty() && s(T.cursor, e.cursor) ? c.substractPoint(e.cursor) : (o ? c.substractPoint(x.cursor) : x && (l.removeSelectionMarker(x), c.addRange(x)), c.addRange(e)), l.inVirtualSelectionMode = !1 }) } else if (S == "block") { e.stop(), l.inVirtualSelectionMode = !0; var N, C = [], k = function () { var e = l.renderer.pixelToScreenCoordinates(m, g), t = b.screenToDocumentPosition(e.row, e.column, e.offsetX); if (s(E, e) && s(t, c.lead)) return; E = e, l.selection.moveToPosition(t), l.renderer.scrollCursorIntoView(), l.removeSelectionMarkers(C), C = c.rectangularRangeBlock(E, w), l.$mouseHandler.$clickSelection && C.length == 1 && C[0].isEmpty() && (C[0] = l.$mouseHandler.$clickSelection.clone()), C.forEach(l.addSelectionMarker, l), l.updateSelectionMarkers() }; h && !a ? c.toSingleRange() : !h && a && (N = c.toOrientedRange(), l.addSelectionMarker(N)), o ? w = b.documentToScreenPosition(c.lead) : c.moveToPosition(p), E = { row: -1, column: -1 }; var L = function (e) { k(), clearInterval(O), l.removeSelectionMarkers(C), C.length || (C = [c.toOrientedRange()]), N && (l.removeSelectionMarker(N), c.toSingleRange(N)); for (var t = 0; t < C.length; t++)c.addRange(C[t]); l.inVirtualSelectionMode = !1, l.$mouseHandler.$clickSelection = null }, A = k; r.capture(l.container, y, L); var O = setInterval(function () { A() }, 20); return e.preventDefault() } } var r = e("../lib/event"), i = e("../lib/useragent"); t.onMouseDown = o }), define("ace/commands/multi_select_commands", ["require", "exports", "module", "ace/keyboard/hash_handler"], function (e, t, n) { t.defaultCommands = [{ name: "addCursorAbove", description: "Add cursor above", exec: function (e) { e.selectMoreLines(-1) }, bindKey: { win: "Ctrl-Alt-Up", mac: "Ctrl-Alt-Up" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorBelow", description: "Add cursor below", exec: function (e) { e.selectMoreLines(1) }, bindKey: { win: "Ctrl-Alt-Down", mac: "Ctrl-Alt-Down" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorAboveSkipCurrent", description: "Add cursor above (skip current)", exec: function (e) { e.selectMoreLines(-1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Up", mac: "Ctrl-Alt-Shift-Up" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "addCursorBelowSkipCurrent", description: "Add cursor below (skip current)", exec: function (e) { e.selectMoreLines(1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Down", mac: "Ctrl-Alt-Shift-Down" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectMoreBefore", description: "Select more before", exec: function (e) { e.selectMore(-1) }, bindKey: { win: "Ctrl-Alt-Left", mac: "Ctrl-Alt-Left" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectMoreAfter", description: "Select more after", exec: function (e) { e.selectMore(1) }, bindKey: { win: "Ctrl-Alt-Right", mac: "Ctrl-Alt-Right" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectNextBefore", description: "Select next before", exec: function (e) { e.selectMore(-1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Left", mac: "Ctrl-Alt-Shift-Left" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "selectNextAfter", description: "Select next after", exec: function (e) { e.selectMore(1, !0) }, bindKey: { win: "Ctrl-Alt-Shift-Right", mac: "Ctrl-Alt-Shift-Right" }, scrollIntoView: "cursor", readOnly: !0 }, { name: "toggleSplitSelectionIntoLines", description: "Split into lines", exec: function (e) { e.multiSelect.rangeCount > 1 ? e.multiSelect.joinSelections() : e.multiSelect.splitIntoLines() }, bindKey: { win: "Ctrl-Alt-L", mac: "Ctrl-Alt-L" }, readOnly: !0 }, { name: "splitSelectionIntoLines", description: "Split into lines", exec: function (e) { e.multiSelect.splitIntoLines() }, readOnly: !0 }, { name: "alignCursors", description: "Align cursors", exec: function (e) { e.alignCursors() }, bindKey: { win: "Ctrl-Alt-A", mac: "Ctrl-Alt-A" }, scrollIntoView: "cursor" }, { name: "findAll", description: "Find all", exec: function (e) { e.findAll() }, bindKey: { win: "Ctrl-Alt-K", mac: "Ctrl-Alt-G" }, scrollIntoView: "cursor", readOnly: !0 }], t.multiSelectCommands = [{ name: "singleSelection", description: "Single selection", bindKey: "esc", exec: function (e) { e.exitMultiSelectMode() }, scrollIntoView: "cursor", readOnly: !0, isAvailable: function (e) { return e && e.inMultiSelectMode } }]; var r = e("../keyboard/hash_handler").HashHandler; t.keyboardHandler = new r(t.multiSelectCommands) }), define("ace/multi_select", ["require", "exports", "module", "ace/range_list", "ace/range", "ace/selection", "ace/mouse/multi_select_handler", "ace/lib/event", "ace/lib/lang", "ace/commands/multi_select_commands", "ace/search", "ace/edit_session", "ace/editor", "ace/config"], function (e, t, n) { function h(e, t, n) { return c.$options.wrap = !0, c.$options.needle = t, c.$options.backwards = n == -1, c.find(e) } function v(e, t) { return e.row == t.row && e.column == t.column } function m(e) { if (e.$multiselectOnSessionChange) return; e.$onAddRange = e.$onAddRange.bind(e), e.$onRemoveRange = e.$onRemoveRange.bind(e), e.$onMultiSelect = e.$onMultiSelect.bind(e), e.$onSingleSelect = e.$onSingleSelect.bind(e), e.$multiselectOnSessionChange = t.onSessionChange.bind(e), e.$checkMultiselectChange = e.$checkMultiselectChange.bind(e), e.$multiselectOnSessionChange(e), e.on("changeSession", e.$multiselectOnSessionChange), e.on("mousedown", o), e.commands.addCommands(f.defaultCommands), g(e) } function g(e) { function r(t) { n && (e.renderer.setMouseCursor(""), n = !1) } if (!e.textInput) return; var t = e.textInput.getElement(), n = !1; u.addListener(t, "keydown", function (t) { var i = t.keyCode == 18 && !(t.ctrlKey || t.shiftKey || t.metaKey); e.$blockSelectEnabled && i ? n || (e.renderer.setMouseCursor("crosshair"), n = !0) : n && r() }, e), u.addListener(t, "keyup", r, e), u.addListener(t, "blur", r, e) } var r = e("./range_list").RangeList, i = e("./range").Range, s = e("./selection").Selection, o = e("./mouse/multi_select_handler").onMouseDown, u = e("./lib/event"), a = e("./lib/lang"), f = e("./commands/multi_select_commands"); t.commands = f.defaultCommands.concat(f.multiSelectCommands); var l = e("./search").Search, c = new l, p = e("./edit_session").EditSession; (function () { this.getSelectionMarkers = function () { return this.$selectionMarkers } }).call(p.prototype), function () { this.ranges = null, this.rangeList = null, this.addRange = function (e, t) { if (!e) return; if (!this.inMultiSelectMode && this.rangeCount === 0) { var n = this.toOrientedRange(); this.rangeList.add(n), this.rangeList.add(e); if (this.rangeList.ranges.length != 2) return this.rangeList.removeAll(), t || this.fromOrientedRange(e); this.rangeList.removeAll(), this.rangeList.add(n), this.$onAddRange(n) } e.cursor || (e.cursor = e.end); var r = this.rangeList.add(e); return this.$onAddRange(e), r.length && this.$onRemoveRange(r), this.rangeCount > 1 && !this.inMultiSelectMode && (this._signal("multiSelect"), this.inMultiSelectMode = !0, this.session.$undoSelect = !1, this.rangeList.attach(this.session)), t || this.fromOrientedRange(e) }, this.toSingleRange = function (e) { e = e || this.ranges[0]; var t = this.rangeList.removeAll(); t.length && this.$onRemoveRange(t), e && this.fromOrientedRange(e) }, this.substractPoint = function (e) { var t = this.rangeList.substractPoint(e); if (t) return this.$onRemoveRange(t), t[0] }, this.mergeOverlappingRanges = function () { var e = this.rangeList.merge(); e.length && this.$onRemoveRange(e) }, this.$onAddRange = function (e) { this.rangeCount = this.rangeList.ranges.length, this.ranges.unshift(e), this._signal("addRange", { range: e }) }, this.$onRemoveRange = function (e) { this.rangeCount = this.rangeList.ranges.length; if (this.rangeCount == 1 && this.inMultiSelectMode) { var t = this.rangeList.ranges.pop(); e.push(t), this.rangeCount = 0 } for (var n = e.length; n--;) { var r = this.ranges.indexOf(e[n]); this.ranges.splice(r, 1) } this._signal("removeRange", { ranges: e }), this.rangeCount === 0 && this.inMultiSelectMode && (this.inMultiSelectMode = !1, this._signal("singleSelect"), this.session.$undoSelect = !0, this.rangeList.detach(this.session)), t = t || this.ranges[0], t && !t.isEqual(this.getRange()) && this.fromOrientedRange(t) }, this.$initRangeList = function () { if (this.rangeList) return; this.rangeList = new r, this.ranges = [], this.rangeCount = 0 }, this.getAllRanges = function () { return this.rangeCount ? this.rangeList.ranges.concat() : [this.getRange()] }, this.splitIntoLines = function () { var e = this.ranges.length ? this.ranges : [this.getRange()], t = []; for (var n = 0; n < e.length; n++) { var r = e[n], s = r.start.row, o = r.end.row; if (s === o) t.push(r.clone()); else { t.push(new i(s, r.start.column, s, this.session.getLine(s).length)); while (++s < o) t.push(this.getLineRange(s, !0)); t.push(new i(o, 0, o, r.end.column)) } n == 0 && !this.isBackwards() && (t = t.reverse()) } this.toSingleRange(); for (var n = t.length; n--;)this.addRange(t[n]) }, this.joinSelections = function () { var e = this.rangeList.ranges, t = e[e.length - 1], n = i.fromPoints(e[0].start, t.end); this.toSingleRange(), this.setSelectionRange(n, t.cursor == t.start) }, this.toggleBlockSelection = function () { if (this.rangeCount > 1) { var e = this.rangeList.ranges, t = e[e.length - 1], n = i.fromPoints(e[0].start, t.end); this.toSingleRange(), this.setSelectionRange(n, t.cursor == t.start) } else { var r = this.session.documentToScreenPosition(this.cursor), s = this.session.documentToScreenPosition(this.anchor), o = this.rectangularRangeBlock(r, s); o.forEach(this.addRange, this) } }, this.rectangularRangeBlock = function (e, t, n) { var r = [], s = e.column < t.column; if (s) var o = e.column, u = t.column, a = e.offsetX, f = t.offsetX; else var o = t.column, u = e.column, a = t.offsetX, f = e.offsetX; var l = e.row < t.row; if (l) var c = e.row, h = t.row; else var c = t.row, h = e.row; o < 0 && (o = 0), c < 0 && (c = 0), c == h && (n = !0); var p; for (var d = c; d <= h; d++) { var m = i.fromPoints(this.session.screenToDocumentPosition(d, o, a), this.session.screenToDocumentPosition(d, u, f)); if (m.isEmpty()) { if (p && v(m.end, p)) break; p = m.end } m.cursor = s ? m.start : m.end, r.push(m) } l && r.reverse(); if (!n) { var g = r.length - 1; while (r[g].isEmpty() && g > 0) g--; if (g > 0) { var y = 0; while (r[y].isEmpty()) y++ } for (var b = g; b >= y; b--)r[b].isEmpty() && r.splice(b, 1) } return r } }.call(s.prototype); var d = e("./editor").Editor; (function () { this.updateSelectionMarkers = function () { this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.addSelectionMarker = function (e) { e.cursor || (e.cursor = e.end); var t = this.getSelectionStyle(); return e.marker = this.session.addMarker(e, "ace_selection", t), this.session.$selectionMarkers.push(e), this.session.selectionMarkerCount = this.session.$selectionMarkers.length, e }, this.removeSelectionMarker = function (e) { if (!e.marker) return; this.session.removeMarker(e.marker); var t = this.session.$selectionMarkers.indexOf(e); t != -1 && this.session.$selectionMarkers.splice(t, 1), this.session.selectionMarkerCount = this.session.$selectionMarkers.length }, this.removeSelectionMarkers = function (e) { var t = this.session.$selectionMarkers; for (var n = e.length; n--;) { var r = e[n]; if (!r.marker) continue; this.session.removeMarker(r.marker); var i = t.indexOf(r); i != -1 && t.splice(i, 1) } this.session.selectionMarkerCount = t.length }, this.$onAddRange = function (e) { this.addSelectionMarker(e.range), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onRemoveRange = function (e) { this.removeSelectionMarkers(e.ranges), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onMultiSelect = function (e) { if (this.inMultiSelectMode) return; this.inMultiSelectMode = !0, this.setStyle("ace_multiselect"), this.keyBinding.addKeyboardHandler(f.keyboardHandler), this.commands.setDefaultHandler("exec", this.$onMultiSelectExec), this.renderer.updateCursor(), this.renderer.updateBackMarkers() }, this.$onSingleSelect = function (e) { if (this.session.multiSelect.inVirtualMode) return; this.inMultiSelectMode = !1, this.unsetStyle("ace_multiselect"), this.keyBinding.removeKeyboardHandler(f.keyboardHandler), this.commands.removeDefaultHandler("exec", this.$onMultiSelectExec), this.renderer.updateCursor(), this.renderer.updateBackMarkers(), this._emit("changeSelection") }, this.$onMultiSelectExec = function (e) { var t = e.command, n = e.editor; if (!n.multiSelect) return; if (!t.multiSelectAction) { var r = t.exec(n, e.args || {}); n.multiSelect.addRange(n.multiSelect.toOrientedRange()), n.multiSelect.mergeOverlappingRanges() } else t.multiSelectAction == "forEach" ? r = n.forEachSelection(t, e.args) : t.multiSelectAction == "forEachLine" ? r = n.forEachSelection(t, e.args, !0) : t.multiSelectAction == "single" ? (n.exitMultiSelectMode(), r = t.exec(n, e.args || {})) : r = t.multiSelectAction(n, e.args || {}); return r }, this.forEachSelection = function (e, t, n) { if (this.inVirtualSelectionMode) return; var r = n && n.keepOrder, i = n == 1 || n && n.$byLines, o = this.session, u = this.selection, a = u.rangeList, f = (r ? u : a).ranges, l; if (!f.length) return e.exec ? e.exec(this, t || {}) : e(this, t || {}); var c = u._eventRegistry; u._eventRegistry = {}; var h = new s(o); this.inVirtualSelectionMode = !0; for (var p = f.length; p--;) { if (i) while (p > 0 && f[p].start.row == f[p - 1].end.row) p--; h.fromOrientedRange(f[p]), h.index = p, this.selection = o.selection = h; var d = e.exec ? e.exec(this, t || {}) : e(this, t || {}); !l && d !== undefined && (l = d), h.toOrientedRange(f[p]) } h.detach(), this.selection = o.selection = u, this.inVirtualSelectionMode = !1, u._eventRegistry = c, u.mergeOverlappingRanges(), u.ranges[0] && u.fromOrientedRange(u.ranges[0]); var v = this.renderer.$scrollAnimation; return this.onCursorChange(), this.onSelectionChange(), v && v.from == v.to && this.renderer.animateScrolling(v.from), l }, this.exitMultiSelectMode = function () { if (!this.inMultiSelectMode || this.inVirtualSelectionMode) return; this.multiSelect.toSingleRange() }, this.getSelectedText = function () { var e = ""; if (this.inMultiSelectMode && !this.inVirtualSelectionMode) { var t = this.multiSelect.rangeList.ranges, n = []; for (var r = 0; r < t.length; r++)n.push(this.session.getTextRange(t[r])); var i = this.session.getDocument().getNewLineCharacter(); e = n.join(i), e.length == (n.length - 1) * i.length && (e = "") } else this.selection.isEmpty() || (e = this.session.getTextRange(this.getSelectionRange())); return e }, this.$checkMultiselectChange = function (e, t) { if (this.inMultiSelectMode && !this.inVirtualSelectionMode) { var n = this.multiSelect.ranges[0]; if (this.multiSelect.isEmpty() && t == this.multiSelect.anchor) return; var r = t == this.multiSelect.anchor ? n.cursor == n.start ? n.end : n.start : n.cursor; r.row != t.row || this.session.$clipPositionToDocument(r.row, r.column).column != t.column ? this.multiSelect.toSingleRange(this.multiSelect.toOrientedRange()) : this.multiSelect.mergeOverlappingRanges() } }, this.findAll = function (e, t, n) { t = t || {}, t.needle = e || t.needle; if (t.needle == undefined) { var r = this.selection.isEmpty() ? this.selection.getWordRange() : this.selection.getRange(); t.needle = this.session.getTextRange(r) } this.$search.set(t); var i = this.$search.findAll(this.session); if (!i.length) return 0; var s = this.multiSelect; n || s.toSingleRange(i[0]); for (var o = i.length; o--;)s.addRange(i[o], !0); return r && s.rangeList.rangeAtPoint(r.start) && s.addRange(r, !0), i.length }, this.selectMoreLines = function (e, t) { var n = this.selection.toOrientedRange(), r = n.cursor == n.end, s = this.session.documentToScreenPosition(n.cursor); this.selection.$desiredColumn && (s.column = this.selection.$desiredColumn); var o = this.session.screenToDocumentPosition(s.row + e, s.column); if (!n.isEmpty()) var u = this.session.documentToScreenPosition(r ? n.end : n.start), a = this.session.screenToDocumentPosition(u.row + e, u.column); else var a = o; if (r) { var f = i.fromPoints(o, a); f.cursor = f.start } else { var f = i.fromPoints(a, o); f.cursor = f.end } f.desiredColumn = s.column; if (!this.selection.inMultiSelectMode) this.selection.addRange(n); else if (t) var l = n.cursor; this.selection.addRange(f), l && this.selection.substractPoint(l) }, this.transposeSelections = function (e) { var t = this.session, n = t.multiSelect, r = n.ranges; for (var i = r.length; i--;) { var s = r[i]; if (s.isEmpty()) { var o = t.getWordRange(s.start.row, s.start.column); s.start.row = o.start.row, s.start.column = o.start.column, s.end.row = o.end.row, s.end.column = o.end.column } } n.mergeOverlappingRanges(); var u = []; for (var i = r.length; i--;) { var s = r[i]; u.unshift(t.getTextRange(s)) } e < 0 ? u.unshift(u.pop()) : u.push(u.shift()); for (var i = r.length; i--;) { var s = r[i], o = s.clone(); t.replace(s, u[i]), s.start.row = o.start.row, s.start.column = o.start.column } n.fromOrientedRange(n.ranges[0]) }, this.selectMore = function (e, t, n) { var r = this.session, i = r.multiSelect, s = i.toOrientedRange(); if (s.isEmpty()) { s = r.getWordRange(s.start.row, s.start.column), s.cursor = e == -1 ? s.start : s.end, this.multiSelect.addRange(s); if (n) return } var o = r.getTextRange(s), u = h(r, o, e); u && (u.cursor = e == -1 ? u.start : u.end, this.session.unfold(u), this.multiSelect.addRange(u), this.renderer.scrollCursorIntoView(null, .5)), t && this.multiSelect.substractPoint(s.cursor) }, this.alignCursors = function () { var e = this.session, t = e.multiSelect, n = t.ranges, r = -1, s = n.filter(function (e) { if (e.cursor.row == r) return !0; r = e.cursor.row }); if (!n.length || s.length == n.length - 1) { var o = this.selection.getRange(), u = o.start.row, f = o.end.row, l = u == f; if (l) { var c = this.session.getLength(), h; do h = this.session.getLine(f); while (/[=:]/.test(h) && ++f < c); do h = this.session.getLine(u); while (/[=:]/.test(h) && --u > 0); u < 0 && (u = 0), f >= c && (f = c - 1) } var p = this.session.removeFullLines(u, f); p = this.$reAlignText(p, l), this.session.insert({ row: u, column: 0 }, p.join("\n") + "\n"), l || (o.start.column = 0, o.end.column = p[p.length - 1].length), this.selection.setRange(o) } else { s.forEach(function (e) { t.substractPoint(e.cursor) }); var d = 0, v = Infinity, m = n.map(function (t) { var n = t.cursor, r = e.getLine(n.row), i = r.substr(n.column).search(/\S/g); return i == -1 && (i = 0), n.column > d && (d = n.column), i < v && (v = i), i }); n.forEach(function (t, n) { var r = t.cursor, s = d - r.column, o = m[n] - v; s > o ? e.insert(r, a.stringRepeat(" ", s - o)) : e.remove(new i(r.row, r.column, r.row, r.column - s + o)), t.start.column = t.end.column = d, t.start.row = t.end.row = r.row, t.cursor = t.end }), t.fromOrientedRange(n[0]), this.renderer.updateCursor(), this.renderer.updateBackMarkers() } }, this.$reAlignText = function (e, t) { function u(e) { return a.stringRepeat(" ", e) } function f(e) { return e[2] ? u(i) + e[2] + u(s - e[2].length + o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } function l(e) { return e[2] ? u(i + s - e[2].length) + e[2] + u(o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } function c(e) { return e[2] ? u(i) + e[2] + u(o) + e[4].replace(/^([=:])\s+/, "$1 ") : e[0] } var n = !0, r = !0, i, s, o; return e.map(function (e) { var t = e.match(/(\s*)(.*?)(\s*)([=:].*)/); return t ? i == null ? (i = t[1].length, s = t[2].length, o = t[3].length, t) : (i + s + o != t[1].length + t[2].length + t[3].length && (r = !1), i != t[1].length && (n = !1), i > t[1].length && (i = t[1].length), s < t[2].length && (s = t[2].length), o > t[3].length && (o = t[3].length), t) : [e] }).map(t ? f : n ? r ? l : f : c) } }).call(d.prototype), t.onSessionChange = function (e) { var t = e.session; t && !t.multiSelect && (t.$selectionMarkers = [], t.selection.$initRangeList(), t.multiSelect = t.selection), this.multiSelect = t && t.multiSelect; var n = e.oldSession; n && (n.multiSelect.off("addRange", this.$onAddRange), n.multiSelect.off("removeRange", this.$onRemoveRange), n.multiSelect.off("multiSelect", this.$onMultiSelect), n.multiSelect.off("singleSelect", this.$onSingleSelect), n.multiSelect.lead.off("change", this.$checkMultiselectChange), n.multiSelect.anchor.off("change", this.$checkMultiselectChange)), t && (t.multiSelect.on("addRange", this.$onAddRange), t.multiSelect.on("removeRange", this.$onRemoveRange), t.multiSelect.on("multiSelect", this.$onMultiSelect), t.multiSelect.on("singleSelect", this.$onSingleSelect), t.multiSelect.lead.on("change", this.$checkMultiselectChange), t.multiSelect.anchor.on("change", this.$checkMultiselectChange)), t && this.inMultiSelectMode != t.selection.inMultiSelectMode && (t.selection.inMultiSelectMode ? this.$onMultiSelect() : this.$onSingleSelect()) }, t.MultiSelect = m, e("./config").defineOptions(d.prototype, "editor", { enableMultiselect: { set: function (e) { m(this), e ? (this.on("changeSession", this.$multiselectOnSessionChange), this.on("mousedown", o)) : (this.off("changeSession", this.$multiselectOnSessionChange), this.off("mousedown", o)) }, value: !0 }, enableBlockSelect: { set: function (e) { this.$blockSelectEnabled = e }, value: !0 } }) }), define("ace/mode/folding/fold_mode", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("../../range").Range, i = t.FoldMode = function () { }; (function () { this.foldingStartMarker = null, this.foldingStopMarker = null, this.getFoldWidget = function (e, t, n) { var r = e.getLine(n); return this.foldingStartMarker.test(r) ? "start" : t == "markbeginend" && this.foldingStopMarker && this.foldingStopMarker.test(r) ? "end" : "" }, this.getFoldWidgetRange = function (e, t, n) { return null }, this.indentationBlock = function (e, t, n) { var i = /\S/, s = e.getLine(t), o = s.search(i); if (o == -1) return; var u = n || s.length, a = e.getLength(), f = t, l = t; while (++t < a) { var c = e.getLine(t).search(i); if (c == -1) continue; if (c <= o) { var h = e.getTokenAt(t, 0); if (!h || h.type !== "string") break } l = t } if (l > f) { var p = e.getLine(l).length; return new r(f, u, l, p) } }, this.openingBracketBlock = function (e, t, n, i, s) { var o = { row: n, column: i + 1 }, u = e.$findClosingBracket(t, o, s); if (!u) return; var a = e.foldWidgets[u.row]; return a == null && (a = e.getFoldWidget(u.row)), a == "start" && u.row > o.row && (u.row--, u.column = e.getLine(u.row).length), r.fromPoints(o, u) }, this.closingBracketBlock = function (e, t, n, i, s) { var o = { row: n, column: i }, u = e.$findOpeningBracket(t, o); if (!u) return; return u.column++, o.column--, r.fromPoints(u, o) } }).call(i.prototype) }), define("ace/theme/textmate", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; t.isDark = !1, t.cssClass = "ace-tm", t.cssText = '.ace-tm .ace_gutter {background: #f0f0f0;color: #333;}.ace-tm .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-tm .ace_fold {background-color: #6B72E6;}.ace-tm {background-color: #FFFFFF;color: black;}.ace-tm .ace_cursor {color: black;}.ace-tm .ace_invisible {color: rgb(191, 191, 191);}.ace-tm .ace_storage,.ace-tm .ace_keyword {color: blue;}.ace-tm .ace_constant {color: rgb(197, 6, 11);}.ace-tm .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-tm .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-tm .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-tm .ace_invalid {background-color: rgba(255, 0, 0, 0.1);color: red;}.ace-tm .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-tm .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-tm .ace_support.ace_type,.ace-tm .ace_support.ace_class {color: rgb(109, 121, 222);}.ace-tm .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-tm .ace_string {color: rgb(3, 106, 7);}.ace-tm .ace_comment {color: rgb(76, 136, 107);}.ace-tm .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-tm .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-tm .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-tm .ace_variable {color: rgb(49, 132, 149);}.ace-tm .ace_xml-pe {color: rgb(104, 104, 91);}.ace-tm .ace_entity.ace_name.ace_function {color: #0000A2;}.ace-tm .ace_heading {color: rgb(12, 7, 255);}.ace-tm .ace_list {color:rgb(185, 6, 144);}.ace-tm .ace_meta.ace_tag {color:rgb(0, 22, 142);}.ace-tm .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-tm .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-tm.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px white;}.ace-tm .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-tm .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-tm .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-tm .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-tm .ace_gutter-active-line {background-color : #dcdcdc;}.ace-tm .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-tm .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}', t.$id = "ace/theme/textmate"; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }), define("ace/line_widgets", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { "use strict"; function i(e) { this.session = e, this.session.widgetManager = this, this.session.getRowLength = this.getRowLength, this.session.$getWidgetScreenLength = this.$getWidgetScreenLength, this.updateOnChange = this.updateOnChange.bind(this), this.renderWidgets = this.renderWidgets.bind(this), this.measureWidgets = this.measureWidgets.bind(this), this.session._changedWidgets = [], this.$onChangeEditor = this.$onChangeEditor.bind(this), this.session.on("change", this.updateOnChange), this.session.on("changeFold", this.updateOnFold), this.session.on("changeEditor", this.$onChangeEditor) } var r = e("./lib/dom"); (function () { this.getRowLength = function (e) { var t; return this.lineWidgets ? t = this.lineWidgets[e] && this.lineWidgets[e].rowCount || 0 : t = 0, !this.$useWrapMode || !this.$wrapData[e] ? 1 + t : this.$wrapData[e].length + 1 + t }, this.$getWidgetScreenLength = function () { var e = 0; return this.lineWidgets.forEach(function (t) { t && t.rowCount && !t.hidden && (e += t.rowCount) }), e }, this.$onChangeEditor = function (e) { this.attach(e.editor) }, this.attach = function (e) { e && e.widgetManager && e.widgetManager != this && e.widgetManager.detach(); if (this.editor == e) return; this.detach(), this.editor = e, e && (e.widgetManager = this, e.renderer.on("beforeRender", this.measureWidgets), e.renderer.on("afterRender", this.renderWidgets)) }, this.detach = function (e) { var t = this.editor; if (!t) return; this.editor = null, t.widgetManager = null, t.renderer.off("beforeRender", this.measureWidgets), t.renderer.off("afterRender", this.renderWidgets); var n = this.session.lineWidgets; n && n.forEach(function (e) { e && e.el && e.el.parentNode && (e._inDocument = !1, e.el.parentNode.removeChild(e.el)) }) }, this.updateOnFold = function (e, t) { var n = t.lineWidgets; if (!n || !e.action) return; var r = e.data, i = r.start.row, s = r.end.row, o = e.action == "add"; for (var u = i + 1; u < s; u++)n[u] && (n[u].hidden = o); n[s] && (o ? n[i] ? n[s].hidden = o : n[i] = n[s] : (n[i] == n[s] && (n[i] = undefined), n[s].hidden = o)) }, this.updateOnChange = function (e) { var t = this.session.lineWidgets; if (!t) return; var n = e.start.row, r = e.end.row - n; if (r !== 0) if (e.action == "remove") { var i = t.splice(n + 1, r); !t[n] && i[i.length - 1] && (t[n] = i.pop()), i.forEach(function (e) { e && this.removeLineWidget(e) }, this), this.$updateRows() } else { var s = new Array(r); t[n] && t[n].column != null && e.start.column > t[n].column && n++, s.unshift(n, 0), t.splice.apply(t, s), this.$updateRows() } }, this.$updateRows = function () { var e = this.session.lineWidgets; if (!e) return; var t = !0; e.forEach(function (e, n) { if (e) { t = !1, e.row = n; while (e.$oldWidget) e.$oldWidget.row = n, e = e.$oldWidget } }), t && (this.session.lineWidgets = null) }, this.$registerLineWidget = function (e) { this.session.lineWidgets || (this.session.lineWidgets = new Array(this.session.getLength())); var t = this.session.lineWidgets[e.row]; return t && (e.$oldWidget = t, t.el && t.el.parentNode && (t.el.parentNode.removeChild(t.el), t._inDocument = !1)), this.session.lineWidgets[e.row] = e, e }, this.addLineWidget = function (e) { this.$registerLineWidget(e), e.session = this.session; if (!this.editor) return e; var t = this.editor.renderer; e.html && !e.el && (e.el = r.createElement("div"), e.el.innerHTML = e.html), e.el && (r.addCssClass(e.el, "ace_lineWidgetContainer"), e.el.style.position = "absolute", e.el.style.zIndex = 5, t.container.appendChild(e.el), e._inDocument = !0, e.coverGutter || (e.el.style.zIndex = 3), e.pixelHeight == null && (e.pixelHeight = e.el.offsetHeight)), e.rowCount == null && (e.rowCount = e.pixelHeight / t.layerConfig.lineHeight); var n = this.session.getFoldAt(e.row, 0); e.$fold = n; if (n) { var i = this.session.lineWidgets; e.row == n.end.row && !i[n.start.row] ? i[n.start.row] = e : e.hidden = !0 } return this.session._emit("changeFold", { data: { start: { row: e.row } } }), this.$updateRows(), this.renderWidgets(null, t), this.onWidgetChanged(e), e }, this.removeLineWidget = function (e) { e._inDocument = !1, e.session = null, e.el && e.el.parentNode && e.el.parentNode.removeChild(e.el); if (e.editor && e.editor.destroy) try { e.editor.destroy() } catch (t) { } if (this.session.lineWidgets) { var n = this.session.lineWidgets[e.row]; if (n == e) this.session.lineWidgets[e.row] = e.$oldWidget, e.$oldWidget && this.onWidgetChanged(e.$oldWidget); else while (n) { if (n.$oldWidget == e) { n.$oldWidget = e.$oldWidget; break } n = n.$oldWidget } } this.session._emit("changeFold", { data: { start: { row: e.row } } }), this.$updateRows() }, this.getWidgetsAtRow = function (e) { var t = this.session.lineWidgets, n = t && t[e], r = []; while (n) r.push(n), n = n.$oldWidget; return r }, this.onWidgetChanged = function (e) { this.session._changedWidgets.push(e), this.editor && this.editor.renderer.updateFull() }, this.measureWidgets = function (e, t) { var n = this.session._changedWidgets, r = t.layerConfig; if (!n || !n.length) return; var i = Infinity; for (var s = 0; s < n.length; s++) { var o = n[s]; if (!o || !o.el) continue; if (o.session != this.session) continue; if (!o._inDocument) { if (this.session.lineWidgets[o.row] != o) continue; o._inDocument = !0, t.container.appendChild(o.el) } o.h = o.el.offsetHeight, o.fixedWidth || (o.w = o.el.offsetWidth, o.screenWidth = Math.ceil(o.w / r.characterWidth)); var u = o.h / r.lineHeight; o.coverLine && (u -= this.session.getRowLineCount(o.row), u < 0 && (u = 0)), o.rowCount != u && (o.rowCount = u, o.row < i && (i = o.row)) } i != Infinity && (this.session._emit("changeFold", { data: { start: { row: i } } }), this.session.lineWidgetWidth = null), this.session._changedWidgets = [] }, this.renderWidgets = function (e, t) { var n = t.layerConfig, r = this.session.lineWidgets; if (!r) return; var i = Math.min(this.firstRow, n.firstRow), s = Math.max(this.lastRow, n.lastRow, r.length); while (i > 0 && !r[i]) i--; this.firstRow = n.firstRow, this.lastRow = n.lastRow, t.$cursorLayer.config = n; for (var o = i; o <= s; o++) { var u = r[o]; if (!u || !u.el) continue; if (u.hidden) { u.el.style.top = -100 - (u.pixelHeight || 0) + "px"; continue } u._inDocument || (u._inDocument = !0, t.container.appendChild(u.el)); var a = t.$cursorLayer.getPixelPosition({ row: o, column: 0 }, !0).top; u.coverLine || (a += n.lineHeight * this.session.getRowLineCount(u.row)), u.el.style.top = a - n.offset + "px"; var f = u.coverGutter ? 0 : t.gutterWidth; u.fixedWidth || (f -= t.scrollLeft), u.el.style.left = f + "px", u.fullWidth && u.screenWidth && (u.el.style.minWidth = n.width + 2 * n.padding + "px"), u.fixedWidth ? u.el.style.right = t.scrollBar.getWidth() + "px" : u.el.style.right = "" } } }).call(i.prototype), t.LineWidgets = i }), define("ace/ext/error_marker", ["require", "exports", "module", "ace/line_widgets", "ace/lib/dom", "ace/range"], function (e, t, n) { "use strict"; function o(e, t, n) { var r = 0, i = e.length - 1; while (r <= i) { var s = r + i >> 1, o = n(t, e[s]); if (o > 0) r = s + 1; else { if (!(o < 0)) return s; i = s - 1 } } return -(r + 1) } function u(e, t, n) { var r = e.getAnnotations().sort(s.comparePoints); if (!r.length) return; var i = o(r, { row: t, column: -1 }, s.comparePoints); i < 0 && (i = -i - 1), i >= r.length ? i = n > 0 ? 0 : r.length - 1 : i === 0 && n < 0 && (i = r.length - 1); var u = r[i]; if (!u || !n) return; if (u.row === t) { do u = r[i += n]; while (u && u.row === t); if (!u) return r.slice() } var a = []; t = u.row; do a[n < 0 ? "unshift" : "push"](u), u = r[i += n]; while (u && u.row == t); return a.length && a } var r = e("../line_widgets").LineWidgets, i = e("../lib/dom"), s = e("../range").Range; t.showErrorMarker = function (e, t) { var n = e.session; n.widgetManager || (n.widgetManager = new r(n), n.widgetManager.attach(e)); var s = e.getCursorPosition(), o = s.row, a = n.widgetManager.getWidgetsAtRow(o).filter(function (e) { return e.type == "errorMarker" })[0]; a ? a.destroy() : o -= t; var f = u(n, o, t), l; if (f) { var c = f[0]; s.column = (c.pos && typeof c.column != "number" ? c.pos.sc : c.column) || 0, s.row = c.row, l = e.renderer.$gutterLayer.$annotations[s.row] } else { if (a) return; l = { text: ["Looks good!"], className: "ace_ok" } } e.session.unfold(s.row), e.selection.moveToPosition(s); var h = { row: s.row, fixedWidth: !0, coverGutter: !0, el: i.createElement("div"), type: "errorMarker" }, p = h.el.appendChild(i.createElement("div")), d = h.el.appendChild(i.createElement("div")); d.className = "error_widget_arrow " + l.className; var v = e.renderer.$cursorLayer.getPixelPosition(s).left; d.style.left = v + e.renderer.gutterWidth - 5 + "px", h.el.className = "error_widget_wrapper", p.className = "error_widget " + l.className, p.innerHTML = l.text.join("
"), p.appendChild(i.createElement("div")); var m = function (e, t, n) { if (t === 0 && (n === "esc" || n === "return")) return h.destroy(), { command: "null" } }; h.destroy = function () { if (e.$mouseHandler.isMousePressed) return; e.keyBinding.removeKeyboardHandler(m), n.widgetManager.removeLineWidget(h), e.off("changeSelection", h.destroy), e.off("changeSession", h.destroy), e.off("mouseup", h.destroy), e.off("change", h.destroy) }, e.keyBinding.addKeyboardHandler(m), e.on("changeSelection", h.destroy), e.on("changeSession", h.destroy), e.on("mouseup", h.destroy), e.on("change", h.destroy), e.session.widgetManager.addLineWidget(h), h.el.onmousedown = e.focus.bind(e), e.renderer.scrollCursorIntoView(null, .5, { bottom: h.el.offsetHeight }) }, i.importCssString(" .error_widget_wrapper { background: inherit; color: inherit; border:none } .error_widget { border-top: solid 2px; border-bottom: solid 2px; margin: 5px 0; padding: 10px 40px; white-space: pre-wrap; } .error_widget.ace_error, .error_widget_arrow.ace_error{ border-color: #ff5a5a } .error_widget.ace_warning, .error_widget_arrow.ace_warning{ border-color: #F1D817 } .error_widget.ace_info, .error_widget_arrow.ace_info{ border-color: #5a5a5a } .error_widget.ace_ok, .error_widget_arrow.ace_ok{ border-color: #5aaa5a } .error_widget_arrow { position: absolute; border: solid 5px; border-top-color: transparent!important; border-right-color: transparent!important; border-left-color: transparent!important; top: -5px; }", "") }), define("ace/ace", ["require", "exports", "module", "ace/lib/fixoldbrowsers", "ace/lib/dom", "ace/lib/event", "ace/range", "ace/editor", "ace/edit_session", "ace/undomanager", "ace/virtual_renderer", "ace/worker/worker_client", "ace/keyboard/hash_handler", "ace/placeholder", "ace/multi_select", "ace/mode/folding/fold_mode", "ace/theme/textmate", "ace/ext/error_marker", "ace/config"], function (e, t, n) { "use strict"; e("./lib/fixoldbrowsers"); var r = e("./lib/dom"), i = e("./lib/event"), s = e("./range").Range, o = e("./editor").Editor, u = e("./edit_session").EditSession, a = e("./undomanager").UndoManager, f = e("./virtual_renderer").VirtualRenderer; e("./worker/worker_client"), e("./keyboard/hash_handler"), e("./placeholder"), e("./multi_select"), e("./mode/folding/fold_mode"), e("./theme/textmate"), e("./ext/error_marker"), t.config = e("./config"), t.require = e, typeof define == "function" && (t.define = define), t.edit = function (e, n) { if (typeof e == "string") { var s = e; e = document.getElementById(s); if (!e) throw new Error("ace.edit can't find div #" + s) } if (e && e.env && e.env.editor instanceof o) return e.env.editor; var u = ""; if (e && /input|textarea/i.test(e.tagName)) { var a = e; u = a.value, e = r.createElement("pre"), a.parentNode.replaceChild(e, a) } else e && (u = e.textContent, e.innerHTML = ""); var l = t.createEditSession(u), c = new o(new f(e), l, n), h = { document: l, editor: c, onResize: c.resize.bind(c, null) }; return a && (h.textarea = a), i.addListener(window, "resize", h.onResize), c.on("destroy", function () { i.removeListener(window, "resize", h.onResize), h.editor.container.env = null }), c.container.env = c.env = h, c }, t.createEditSession = function (e, t) { var n = new u(e, t); return n.setUndoManager(new a), n }, t.Range = s, t.Editor = o, t.EditSession = u, t.UndoManager = a, t.VirtualRenderer = f, t.version = t.config.version }); (function () { - window.require(["ace/ace"], function (a) { - if (a) { - a.config.init(true); - a.define = window.define; - } - if (!window.ace) - window.ace = a; - for (var key in a) if (a.hasOwnProperty(key)) - window.ace[key] = a[key]; - window.ace["default"] = window.ace; - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = window.ace; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js b/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js deleted file mode 100644 index cf28c3b012..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/ext-searchbox.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/ext/searchbox", ["require", "exports", "module", "ace/lib/dom", "ace/lib/lang", "ace/lib/event", "ace/keyboard/hash_handler", "ace/lib/keys"], function (e, t, n) { "use strict"; var r = e("../lib/dom"), i = e("../lib/lang"), s = e("../lib/event"), o = '.ace_search {background-color: #ddd;color: #666;border: 1px solid #cbcbcb;border-top: 0 none;overflow: hidden;margin: 0;padding: 4px 6px 0 4px;position: absolute;top: 0;z-index: 99;white-space: normal;}.ace_search.left {border-left: 0 none;border-radius: 0px 0px 5px 0px;left: 0;}.ace_search.right {border-radius: 0px 0px 0px 5px;border-right: 0 none;right: 0;}.ace_search_form, .ace_replace_form {margin: 0 20px 4px 0;overflow: hidden;line-height: 1.9;}.ace_replace_form {margin-right: 0;}.ace_search_form.ace_nomatch {outline: 1px solid red;}.ace_search_field {border-radius: 3px 0 0 3px;background-color: white;color: black;border: 1px solid #cbcbcb;border-right: 0 none;outline: 0;padding: 0;font-size: inherit;margin: 0;line-height: inherit;padding: 0 6px;min-width: 17em;vertical-align: top;min-height: 1.8em;box-sizing: content-box;}.ace_searchbtn {border: 1px solid #cbcbcb;line-height: inherit;display: inline-block;padding: 0 6px;background: #fff;border-right: 0 none;border-left: 1px solid #dcdcdc;cursor: pointer;margin: 0;position: relative;color: #666;}.ace_searchbtn:last-child {border-radius: 0 3px 3px 0;border-right: 1px solid #cbcbcb;}.ace_searchbtn:disabled {background: none;cursor: default;}.ace_searchbtn:hover {background-color: #eef1f6;}.ace_searchbtn.prev, .ace_searchbtn.next {padding: 0px 0.7em}.ace_searchbtn.prev:after, .ace_searchbtn.next:after {content: "";border: solid 2px #888;width: 0.5em;height: 0.5em;border-width: 2px 0 0 2px;display:inline-block;transform: rotate(-45deg);}.ace_searchbtn.next:after {border-width: 0 2px 2px 0 ;}.ace_searchbtn_close {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAcCAYAAABRVo5BAAAAZ0lEQVR42u2SUQrAMAhDvazn8OjZBilCkYVVxiis8H4CT0VrAJb4WHT3C5xU2a2IQZXJjiQIRMdkEoJ5Q2yMqpfDIo+XY4k6h+YXOyKqTIj5REaxloNAd0xiKmAtsTHqW8sR2W5f7gCu5nWFUpVjZwAAAABJRU5ErkJggg==) no-repeat 50% 0;border-radius: 50%;border: 0 none;color: #656565;cursor: pointer;font: 16px/16px Arial;padding: 0;height: 14px;width: 14px;top: 9px;right: 7px;position: absolute;}.ace_searchbtn_close:hover {background-color: #656565;background-position: 50% 100%;color: white;}.ace_button {margin-left: 2px;cursor: pointer;-webkit-user-select: none;-moz-user-select: none;-o-user-select: none;-ms-user-select: none;user-select: none;overflow: hidden;opacity: 0.7;border: 1px solid rgba(100,100,100,0.23);padding: 1px;box-sizing: border-box!important;color: black;}.ace_button:hover {background-color: #eee;opacity:1;}.ace_button:active {background-color: #ddd;}.ace_button.checked {border-color: #3399ff;opacity:1;}.ace_search_options{margin-bottom: 3px;text-align: right;-webkit-user-select: none;-moz-user-select: none;-o-user-select: none;-ms-user-select: none;user-select: none;clear: both;}.ace_search_counter {float: left;font-family: arial;padding: 0 8px;}', u = e("../keyboard/hash_handler").HashHandler, a = e("../lib/keys"), f = 999; r.importCssString(o, "ace_searchbox"); var l = function (e, t, n) { var i = r.createElement("div"); r.buildDom(["div", { "class": "ace_search right" }, ["span", { action: "hide", "class": "ace_searchbtn_close" }], ["div", { "class": "ace_search_form" }, ["input", { "class": "ace_search_field", placeholder: "Search for", spellcheck: "false" }], ["span", { action: "findPrev", "class": "ace_searchbtn prev" }, "\u200b"], ["span", { action: "findNext", "class": "ace_searchbtn next" }, "\u200b"], ["span", { action: "findAll", "class": "ace_searchbtn", title: "Alt-Enter" }, "All"]], ["div", { "class": "ace_replace_form" }, ["input", { "class": "ace_search_field", placeholder: "Replace with", spellcheck: "false" }], ["span", { action: "replaceAndFindNext", "class": "ace_searchbtn" }, "Replace"], ["span", { action: "replaceAll", "class": "ace_searchbtn" }, "All"]], ["div", { "class": "ace_search_options" }, ["span", { action: "toggleReplace", "class": "ace_button", title: "Toggle Replace mode", style: "float:left;margin-top:-2px;padding:0 5px;" }, "+"], ["span", { "class": "ace_search_counter" }], ["span", { action: "toggleRegexpMode", "class": "ace_button", title: "RegExp Search" }, ".*"], ["span", { action: "toggleCaseSensitive", "class": "ace_button", title: "CaseSensitive Search" }, "Aa"], ["span", { action: "toggleWholeWords", "class": "ace_button", title: "Whole Word Search" }, "\\b"], ["span", { action: "searchInSelection", "class": "ace_button", title: "Search In Selection" }, "S"]]], i), this.element = i.firstChild, this.setSession = this.setSession.bind(this), this.$init(), this.setEditor(e), r.importCssString(o, "ace_searchbox", e.container) }; (function () { this.setEditor = function (e) { e.searchBox = this, e.renderer.scroller.appendChild(this.element), this.editor = e }, this.setSession = function (e) { this.searchRange = null, this.$syncOptions(!0) }, this.$initElements = function (e) { this.searchBox = e.querySelector(".ace_search_form"), this.replaceBox = e.querySelector(".ace_replace_form"), this.searchOption = e.querySelector("[action=searchInSelection]"), this.replaceOption = e.querySelector("[action=toggleReplace]"), this.regExpOption = e.querySelector("[action=toggleRegexpMode]"), this.caseSensitiveOption = e.querySelector("[action=toggleCaseSensitive]"), this.wholeWordOption = e.querySelector("[action=toggleWholeWords]"), this.searchInput = this.searchBox.querySelector(".ace_search_field"), this.replaceInput = this.replaceBox.querySelector(".ace_search_field"), this.searchCounter = e.querySelector(".ace_search_counter") }, this.$init = function () { var e = this.element; this.$initElements(e); var t = this; s.addListener(e, "mousedown", function (e) { setTimeout(function () { t.activeInput.focus() }, 0), s.stopPropagation(e) }), s.addListener(e, "click", function (e) { var n = e.target || e.srcElement, r = n.getAttribute("action"); r && t[r] ? t[r]() : t.$searchBarKb.commands[r] && t.$searchBarKb.commands[r].exec(t), s.stopPropagation(e) }), s.addCommandKeyListener(e, function (e, n, r) { var i = a.keyCodeToString(r), o = t.$searchBarKb.findKeyCommand(n, i); o && o.exec && (o.exec(t), s.stopEvent(e)) }), this.$onChange = i.delayedCall(function () { t.find(!1, !1) }), s.addListener(this.searchInput, "input", function () { t.$onChange.schedule(20) }), s.addListener(this.searchInput, "focus", function () { t.activeInput = t.searchInput, t.searchInput.value && t.highlight() }), s.addListener(this.replaceInput, "focus", function () { t.activeInput = t.replaceInput, t.searchInput.value && t.highlight() }) }, this.$closeSearchBarKb = new u([{ bindKey: "Esc", name: "closeSearchBar", exec: function (e) { e.searchBox.hide() } }]), this.$searchBarKb = new u, this.$searchBarKb.bindKeys({ "Ctrl-f|Command-f": function (e) { var t = e.isReplace = !e.isReplace; e.replaceBox.style.display = t ? "" : "none", e.replaceOption.checked = !1, e.$syncOptions(), e.searchInput.focus() }, "Ctrl-H|Command-Option-F": function (e) { if (e.editor.getReadOnly()) return; e.replaceOption.checked = !0, e.$syncOptions(), e.replaceInput.focus() }, "Ctrl-G|Command-G": function (e) { e.findNext() }, "Ctrl-Shift-G|Command-Shift-G": function (e) { e.findPrev() }, esc: function (e) { setTimeout(function () { e.hide() }) }, Return: function (e) { e.activeInput == e.replaceInput && e.replace(), e.findNext() }, "Shift-Return": function (e) { e.activeInput == e.replaceInput && e.replace(), e.findPrev() }, "Alt-Return": function (e) { e.activeInput == e.replaceInput && e.replaceAll(), e.findAll() }, Tab: function (e) { (e.activeInput == e.replaceInput ? e.searchInput : e.replaceInput).focus() } }), this.$searchBarKb.addCommands([{ name: "toggleRegexpMode", bindKey: { win: "Alt-R|Alt-/", mac: "Ctrl-Alt-R|Ctrl-Alt-/" }, exec: function (e) { e.regExpOption.checked = !e.regExpOption.checked, e.$syncOptions() } }, { name: "toggleCaseSensitive", bindKey: { win: "Alt-C|Alt-I", mac: "Ctrl-Alt-R|Ctrl-Alt-I" }, exec: function (e) { e.caseSensitiveOption.checked = !e.caseSensitiveOption.checked, e.$syncOptions() } }, { name: "toggleWholeWords", bindKey: { win: "Alt-B|Alt-W", mac: "Ctrl-Alt-B|Ctrl-Alt-W" }, exec: function (e) { e.wholeWordOption.checked = !e.wholeWordOption.checked, e.$syncOptions() } }, { name: "toggleReplace", exec: function (e) { e.replaceOption.checked = !e.replaceOption.checked, e.$syncOptions() } }, { name: "searchInSelection", exec: function (e) { e.searchOption.checked = !e.searchRange, e.setSearchRange(e.searchOption.checked && e.editor.getSelectionRange()), e.$syncOptions() } }]), this.setSearchRange = function (e) { this.searchRange = e, e ? this.searchRangeMarker = this.editor.session.addMarker(e, "ace_active-line") : this.searchRangeMarker && (this.editor.session.removeMarker(this.searchRangeMarker), this.searchRangeMarker = null) }, this.$syncOptions = function (e) { r.setCssClass(this.replaceOption, "checked", this.searchRange), r.setCssClass(this.searchOption, "checked", this.searchOption.checked), this.replaceOption.textContent = this.replaceOption.checked ? "-" : "+", r.setCssClass(this.regExpOption, "checked", this.regExpOption.checked), r.setCssClass(this.wholeWordOption, "checked", this.wholeWordOption.checked), r.setCssClass(this.caseSensitiveOption, "checked", this.caseSensitiveOption.checked); var t = this.editor.getReadOnly(); this.replaceOption.style.display = t ? "none" : "", this.replaceBox.style.display = this.replaceOption.checked && !t ? "" : "none", this.find(!1, !1, e) }, this.highlight = function (e) { this.editor.session.highlight(e || this.editor.$search.$options.re), this.editor.renderer.updateBackMarkers() }, this.find = function (e, t, n) { var i = this.editor.find(this.searchInput.value, { skipCurrent: e, backwards: t, wrap: !0, regExp: this.regExpOption.checked, caseSensitive: this.caseSensitiveOption.checked, wholeWord: this.wholeWordOption.checked, preventScroll: n, range: this.searchRange }), s = !i && this.searchInput.value; r.setCssClass(this.searchBox, "ace_nomatch", s), this.editor._emit("findSearchBox", { match: !s }), this.highlight(), this.updateCounter() }, this.updateCounter = function () { var e = this.editor, t = e.$search.$options.re, n = 0, r = 0; if (t) { var i = this.searchRange ? e.session.getTextRange(this.searchRange) : e.getValue(), s = e.session.doc.positionToIndex(e.selection.anchor); this.searchRange && (s -= e.session.doc.positionToIndex(this.searchRange.start)); var o = t.lastIndex = 0, u; while (u = t.exec(i)) { n++, o = u.index, o <= s && r++; if (n > f) break; if (!u[0]) { t.lastIndex = o += 1; if (o >= i.length) break } } } this.searchCounter.textContent = r + " of " + (n > f ? f + "+" : n) }, this.findNext = function () { this.find(!0, !1) }, this.findPrev = function () { this.find(!0, !0) }, this.findAll = function () { var e = this.editor.findAll(this.searchInput.value, { regExp: this.regExpOption.checked, caseSensitive: this.caseSensitiveOption.checked, wholeWord: this.wholeWordOption.checked }), t = !e && this.searchInput.value; r.setCssClass(this.searchBox, "ace_nomatch", t), this.editor._emit("findSearchBox", { match: !t }), this.highlight(), this.hide() }, this.replace = function () { this.editor.getReadOnly() || this.editor.replace(this.replaceInput.value) }, this.replaceAndFindNext = function () { this.editor.getReadOnly() || (this.editor.replace(this.replaceInput.value), this.findNext()) }, this.replaceAll = function () { this.editor.getReadOnly() || this.editor.replaceAll(this.replaceInput.value) }, this.hide = function () { this.active = !1, this.setSearchRange(null), this.editor.off("changeSession", this.setSession), this.element.style.display = "none", this.editor.keyBinding.removeKeyboardHandler(this.$closeSearchBarKb), this.editor.focus() }, this.show = function (e, t) { this.active = !0, this.editor.on("changeSession", this.setSession), this.element.style.display = "", this.replaceOption.checked = t, e && (this.searchInput.value = e), this.searchInput.focus(), this.searchInput.select(), this.editor.keyBinding.addKeyboardHandler(this.$closeSearchBarKb), this.$syncOptions(!0) }, this.isFocused = function () { var e = document.activeElement; return e == this.searchInput || e == this.replaceInput } }).call(l.prototype), t.SearchBox = l, t.Search = function (e, t) { var n = e.searchBox || new l(e); n.show(e.session.getTextRange(), t) } }); (function () { - window.require(["ace/ext/searchbox"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/mode-yaml.js b/esphome/dashboard/static/js/vendor/ace/mode-yaml.js deleted file mode 100644 index 81cf343aa5..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/mode-yaml.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/mode/yaml_highlight_rules", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("./text_highlight_rules").TextHighlightRules, s = function () { this.$rules = { start: [{ token: "comment", regex: "#.*$" }, { token: "list.markup", regex: /^(?:-{3}|\.{3})\s*(?=#|$)/ }, { token: "list.markup", regex: /^\s*[\-?](?:$|\s)/ }, { token: "constant", regex: "!![\\w//]+" }, { token: "constant.language", regex: "[&\\*][a-zA-Z0-9-_]+" }, { token: ["meta.tag", "keyword"], regex: /^(\s*\w.*?)(:(?=\s|$))/ }, { token: ["meta.tag", "keyword"], regex: /(\w+?)(\s*:(?=\s|$))/ }, { token: "keyword.operator", regex: "<<\\w*:\\w*" }, { token: "keyword.operator", regex: "-\\s*(?=[{])" }, { token: "string", regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' }, { token: "string", regex: /[|>][-+\d]*(?:$|\s+(?:$|#))/, onMatch: function (e, t, n, r) { r = r.replace(/ #.*/, ""); var i = /^ *((:\s*)?-(\s*[^|>])?)?/.exec(r)[0].replace(/\S\s*$/, "").length, s = parseInt(/\d+[\s+-]*$/.exec(r)); return s ? (i += s - 1, this.next = "mlString") : this.next = "mlStringPre", n.length ? (n[0] = this.next, n[1] = i) : (n.push(this.next), n.push(i)), this.token }, next: "mlString" }, { token: "string", regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']" }, { token: "constant.numeric", regex: /(\b|[+\-\.])[\d_]+(?:(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)(?=[^\d-\w]|$)/ }, { token: "constant.numeric", regex: /[+\-]?\.inf\b|NaN\b|0x[\dA-Fa-f_]+|0b[10_]+/ }, { token: "constant.language.boolean", regex: "\\b(?:true|false|TRUE|FALSE|True|False|yes|no)\\b" }, { token: "paren.lparen", regex: "[[({]" }, { token: "paren.rparen", regex: "[\\])}]" }, { token: "text", regex: /[^\s,:\[\]\{\}]+/ }], mlStringPre: [{ token: "indent", regex: /^ *$/ }, { token: "indent", regex: /^ */, onMatch: function (e, t, n) { var r = n[1]; return r >= e.length ? (this.next = "start", n.shift(), n.shift()) : (n[1] = e.length - 1, this.next = n[0] = "mlString"), this.token }, next: "mlString" }, { defaultToken: "string" }], mlString: [{ token: "indent", regex: /^ *$/ }, { token: "indent", regex: /^ */, onMatch: function (e, t, n) { var r = n[1]; return r >= e.length ? (this.next = "start", n.splice(0)) : this.next = "mlString", this.token }, next: "mlString" }, { token: "string", regex: ".+" }] }, this.normalizeRules() }; r.inherits(s, i), t.YamlHighlightRules = s }), define("ace/mode/matching_brace_outdent", ["require", "exports", "module", "ace/range"], function (e, t, n) { "use strict"; var r = e("../range").Range, i = function () { }; (function () { this.checkOutdent = function (e, t) { return /^\s+$/.test(e) ? /^\s*\}/.test(t) : !1 }, this.autoOutdent = function (e, t) { var n = e.getLine(t), i = n.match(/^(\s*\})/); if (!i) return 0; var s = i[1].length, o = e.findMatchingBracket({ row: t, column: s }); if (!o || o.row == t) return 0; var u = this.$getIndent(e.getLine(o.row)); e.replace(new r(t, 0, t, s - 1), u) }, this.$getIndent = function (e) { return e.match(/^\s*/)[0] } }).call(i.prototype), t.MatchingBraceOutdent = i }), define("ace/mode/folding/coffee", ["require", "exports", "module", "ace/lib/oop", "ace/mode/folding/fold_mode", "ace/range"], function (e, t, n) { "use strict"; var r = e("../../lib/oop"), i = e("./fold_mode").FoldMode, s = e("../../range").Range, o = t.FoldMode = function () { }; r.inherits(o, i), function () { this.getFoldWidgetRange = function (e, t, n) { var r = this.indentationBlock(e, n); if (r) return r; var i = /\S/, o = e.getLine(n), u = o.search(i); if (u == -1 || o[u] != "#") return; var a = o.length, f = e.getLength(), l = n, c = n; while (++n < f) { o = e.getLine(n); var h = o.search(i); if (h == -1) continue; if (o[h] != "#") break; c = n } if (c > l) { var p = e.getLine(c).length; return new s(l, a, c, p) } }, this.getFoldWidget = function (e, t, n) { var r = e.getLine(n), i = r.search(/\S/), s = e.getLine(n + 1), o = e.getLine(n - 1), u = o.search(/\S/), a = s.search(/\S/); if (i == -1) return e.foldWidgets[n - 1] = u != -1 && u < a ? "start" : "", ""; if (u == -1) { if (i == a && r[i] == "#" && s[i] == "#") return e.foldWidgets[n - 1] = "", e.foldWidgets[n + 1] = "", "start" } else if (u == i && r[i] == "#" && o[i] == "#" && e.getLine(n - 2).search(/\S/) == -1) return e.foldWidgets[n - 1] = "start", e.foldWidgets[n + 1] = "", ""; return u != -1 && u < i ? e.foldWidgets[n - 1] = "start" : e.foldWidgets[n - 1] = "", i < a ? "start" : "" } }.call(o.prototype) }), define("ace/mode/yaml", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text", "ace/mode/yaml_highlight_rules", "ace/mode/matching_brace_outdent", "ace/mode/folding/coffee"], function (e, t, n) { "use strict"; var r = e("../lib/oop"), i = e("./text").Mode, s = e("./yaml_highlight_rules").YamlHighlightRules, o = e("./matching_brace_outdent").MatchingBraceOutdent, u = e("./folding/coffee").FoldMode, a = function () { this.HighlightRules = s, this.$outdent = new o, this.foldingRules = new u, this.$behaviour = this.$defaultBehaviour }; r.inherits(a, i), function () { this.lineCommentStart = ["#"], this.getNextLineIndent = function (e, t, n) { var r = this.$getIndent(t); if (e == "start") { var i = t.match(/^.*[\{\(\[]\s*$/); i && (r += n) } return r }, this.checkOutdent = function (e, t, n) { return this.$outdent.checkOutdent(t, n) }, this.autoOutdent = function (e, t, n) { this.$outdent.autoOutdent(t, n) }, this.$id = "ace/mode/yaml" }.call(a.prototype), t.Mode = a }); (function () { - window.require(["ace/mode/yaml"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js b/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js deleted file mode 100644 index 2335f6d773..0000000000 --- a/esphome/dashboard/static/js/vendor/ace/theme-dreamweaver.js +++ /dev/null @@ -1,7 +0,0 @@ -define("ace/theme/dreamweaver", ["require", "exports", "module", "ace/lib/dom"], function (e, t, n) { t.isDark = !1, t.cssClass = "ace-dreamweaver", t.cssText = '.ace-dreamweaver .ace_gutter {background: #e8e8e8;color: #333;}.ace-dreamweaver .ace_print-margin {width: 1px;background: #e8e8e8;}.ace-dreamweaver {background-color: #FFFFFF;color: black;}.ace-dreamweaver .ace_fold {background-color: #757AD8;}.ace-dreamweaver .ace_cursor {color: black;}.ace-dreamweaver .ace_invisible {color: rgb(191, 191, 191);}.ace-dreamweaver .ace_storage,.ace-dreamweaver .ace_keyword {color: blue;}.ace-dreamweaver .ace_constant.ace_buildin {color: rgb(88, 72, 246);}.ace-dreamweaver .ace_constant.ace_language {color: rgb(88, 92, 246);}.ace-dreamweaver .ace_constant.ace_library {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_invalid {background-color: rgb(153, 0, 0);color: white;}.ace-dreamweaver .ace_support.ace_function {color: rgb(60, 76, 114);}.ace-dreamweaver .ace_support.ace_constant {color: rgb(6, 150, 14);}.ace-dreamweaver .ace_support.ace_type,.ace-dreamweaver .ace_support.ace_class {color: #009;}.ace-dreamweaver .ace_support.ace_php_tag {color: #f00;}.ace-dreamweaver .ace_keyword.ace_operator {color: rgb(104, 118, 135);}.ace-dreamweaver .ace_string {color: #00F;}.ace-dreamweaver .ace_comment {color: rgb(76, 136, 107);}.ace-dreamweaver .ace_comment.ace_doc {color: rgb(0, 102, 255);}.ace-dreamweaver .ace_comment.ace_doc.ace_tag {color: rgb(128, 159, 191);}.ace-dreamweaver .ace_constant.ace_numeric {color: rgb(0, 0, 205);}.ace-dreamweaver .ace_variable {color: #06F}.ace-dreamweaver .ace_xml-pe {color: rgb(104, 104, 91);}.ace-dreamweaver .ace_entity.ace_name.ace_function {color: #00F;}.ace-dreamweaver .ace_heading {color: rgb(12, 7, 255);}.ace-dreamweaver .ace_list {color:rgb(185, 6, 144);}.ace-dreamweaver .ace_marker-layer .ace_selection {background: rgb(181, 213, 255);}.ace-dreamweaver .ace_marker-layer .ace_step {background: rgb(252, 255, 0);}.ace-dreamweaver .ace_marker-layer .ace_stack {background: rgb(164, 229, 101);}.ace-dreamweaver .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid rgb(192, 192, 192);}.ace-dreamweaver .ace_marker-layer .ace_active-line {background: rgba(0, 0, 0, 0.07);}.ace-dreamweaver .ace_gutter-active-line {background-color : #DCDCDC;}.ace-dreamweaver .ace_marker-layer .ace_selected-word {background: rgb(250, 250, 255);border: 1px solid rgb(200, 200, 250);}.ace-dreamweaver .ace_meta.ace_tag {color:#009;}.ace-dreamweaver .ace_meta.ace_tag.ace_anchor {color:#060;}.ace-dreamweaver .ace_meta.ace_tag.ace_form {color:#F90;}.ace-dreamweaver .ace_meta.ace_tag.ace_image {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_script {color:#900;}.ace-dreamweaver .ace_meta.ace_tag.ace_style {color:#909;}.ace-dreamweaver .ace_meta.ace_tag.ace_table {color:#099;}.ace-dreamweaver .ace_string.ace_regex {color: rgb(255, 0, 0)}.ace-dreamweaver .ace_indent-guide {background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y;}'; var r = e("../lib/dom"); r.importCssString(t.cssText, t.cssClass) }); (function () { - window.require(["ace/theme/dreamweaver"], function (m) { - if (typeof module == "object" && typeof exports == "object" && module) { - module.exports = m; - } - }); -})(); diff --git a/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js b/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js deleted file mode 100644 index 862a649869..0000000000 --- a/esphome/dashboard/static/js/vendor/jquery-ui/jquery-ui.min.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2016-09-14 -* http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("

"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) -}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; -this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("'),this.$slides.each(function(t,e){var i=s('
  • ');n.$indicators.append(i[0])}),this.$el.append(this.$indicators[0]),this.$indicators=this.$indicators.children("li.indicator-item"))}},{key:"_removeIndicators",value:function(){this.$el.find("ul.indicators").remove()}},{key:"set",value:function(t){var e=this;if(t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.activeIndex!=t){this.$active=this.$slides.eq(this.activeIndex);var i=this.$active.find(".caption");this.$active.removeClass("active"),o({targets:this.$active[0],opacity:0,duration:this.options.duration,easing:"easeOutQuad",complete:function(){e.$slides.not(".active").each(function(t){o({targets:t,opacity:0,translateX:0,translateY:0,duration:0,easing:"easeOutQuad"})})}}),this._animateCaptionIn(i[0],this.options.duration),this.options.indicators&&(this.$indicators.eq(this.activeIndex).removeClass("active"),this.$indicators.eq(t).addClass("active")),o({targets:this.$slides.eq(t)[0],opacity:1,duration:this.options.duration,easing:"easeOutQuad"}),o({targets:this.$slides.eq(t).find(".caption")[0],opacity:1,translateX:0,translateY:0,duration:this.options.duration,delay:this.options.duration,easing:"easeOutQuad"}),this.$slides.eq(t).addClass("active"),this.activeIndex=t,this.start()}}},{key:"pause",value:function(){clearInterval(this.interval)}},{key:"start",value:function(){clearInterval(this.interval),this.interval=setInterval(this._handleIntervalBound,this.options.duration+this.options.interval)}},{key:"next",value:function(){var t=this.activeIndex+1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}},{key:"prev",value:function(){var t=this.activeIndex-1;t>=this.$slides.length?t=0:t<0&&(t=this.$slides.length-1),this.set(t)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Slider}},{key:"defaults",get:function(){return e}}]),n}();M.Slider=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"slider","M_Slider")}(cash,M.anime),function(n,s){n(document).on("click",".card",function(t){if(n(this).children(".card-reveal").length){var i=n(t.target).closest(".card");void 0===i.data("initialOverflow")&&i.data("initialOverflow",void 0===i.css("overflow")?"":i.css("overflow"));var e=n(this).find(".card-reveal");n(t.target).is(n(".card-reveal .card-title"))||n(t.target).is(n(".card-reveal .card-title i"))?s({targets:e[0],translateY:0,duration:225,easing:"easeInOutQuad",complete:function(t){var e=t.animatables[0].target;n(e).css({display:"none"}),i.css("overflow",i.data("initialOverflow"))}}):(n(t.target).is(n(".card .activator"))||n(t.target).is(n(".card .activator i")))&&(i.css("overflow","hidden"),e.css({display:"block"}),s({targets:e[0],translateY:"-100%",duration:300,easing:"easeInOutQuad"}))}})}(cash,M.anime),function(h){"use strict";var e={data:[],placeholder:"",secondaryPlaceholder:"",autocompleteOptions:{},limit:1/0,onChipAdd:null,onChipSelect:null,onChipDelete:null},t=function(t){function l(t,e){_classCallCheck(this,l);var i=_possibleConstructorReturn(this,(l.__proto__||Object.getPrototypeOf(l)).call(this,l,t,e));return(i.el.M_Chips=i).options=h.extend({},l.defaults,e),i.$el.addClass("chips input-field"),i.chipsData=[],i.$chips=h(),i._setupInput(),i.hasAutocomplete=0"),this.$el.append(this.$input)),this.$input.addClass("input")}},{key:"_setupLabel",value:function(){this.$label=this.$el.find("label"),this.$label.length&&this.$label.setAttribute("for",this.$input.attr("id"))}},{key:"_setPlaceholder",value:function(){void 0!==this.chipsData&&!this.chipsData.length&&this.options.placeholder?h(this.$input).prop("placeholder",this.options.placeholder):(void 0===this.chipsData||this.chipsData.length)&&this.options.secondaryPlaceholder&&h(this.$input).prop("placeholder",this.options.secondaryPlaceholder)}},{key:"_isValid",value:function(t){if(t.hasOwnProperty("tag")&&""!==t.tag){for(var e=!1,i=0;i=this.options.limit)){var e=this._renderChip(t);this.$chips.add(e),this.chipsData.push(t),h(this.$input).before(e),this._setPlaceholder(),"function"==typeof this.options.onChipAdd&&this.options.onChipAdd.call(this,this.$el,e)}}},{key:"deleteChip",value:function(t){var e=this.$chips.eq(t);this.$chips.eq(t).remove(),this.$chips=this.$chips.filter(function(t){return 0<=h(t).index()}),this.chipsData.splice(t,1),this._setPlaceholder(),"function"==typeof this.options.onChipDelete&&this.options.onChipDelete.call(this,this.$el,e[0])}},{key:"selectChip",value:function(t){var e=this.$chips.eq(t);(this._selectedChip=e)[0].focus(),"function"==typeof this.options.onChipSelect&&this.options.onChipSelect.call(this,this.$el,e[0])}}],[{key:"init",value:function(t,e){return _get(l.__proto__||Object.getPrototypeOf(l),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Chips}},{key:"_handleChipsKeydown",value:function(t){l._keydown=!0;var e=h(t.target).closest(".chips"),i=t.target&&e.length;if(!h(t.target).is("input, textarea")&&i){var n=e[0].M_Chips;if(8===t.keyCode||46===t.keyCode){t.preventDefault();var s=n.chipsData.length;if(n._selectedChip){var o=n._selectedChip.index();n.deleteChip(o),n._selectedChip=null,s=Math.max(o-1,0)}n.chipsData.length&&n.selectChip(s)}else if(37===t.keyCode){if(n._selectedChip){var a=n._selectedChip.index()-1;if(a<0)return;n.selectChip(a)}}else if(39===t.keyCode&&n._selectedChip){var r=n._selectedChip.index()+1;r>=n.chipsData.length?n.$input[0].focus():n.selectChip(r)}}}},{key:"_handleChipsKeyup",value:function(t){l._keydown=!1}},{key:"_handleChipsBlur",value:function(t){l._keydown||(h(t.target).closest(".chips")[0].M_Chips._selectedChip=null)}},{key:"defaults",get:function(){return e}}]),l}();t._keydown=!1,M.Chips=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"chips","M_Chips"),h(document).ready(function(){h(document.body).on("click",".chip .close",function(){var t=h(this).closest(".chips");t.length&&t[0].M_Chips||h(this).closest(".chip").remove()})})}(cash),function(s){"use strict";var e={top:0,bottom:1/0,offset:0,onPositionChange:null},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Pushpin=i).options=s.extend({},n.defaults,e),i.originalOffset=i.el.offsetTop,n._pushpins.push(i),i._setupEventHandlers(),i._updatePosition(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this.el.style.top=null,this._removePinClasses(),this._removeEventHandlers();var t=n._pushpins.indexOf(this);n._pushpins.splice(t,1)}},{key:"_setupEventHandlers",value:function(){document.addEventListener("scroll",n._updateElements)}},{key:"_removeEventHandlers",value:function(){document.removeEventListener("scroll",n._updateElements)}},{key:"_updatePosition",value:function(){var t=M.getDocumentScrollTop()+this.options.offset;this.options.top<=t&&this.options.bottom>=t&&!this.el.classList.contains("pinned")&&(this._removePinClasses(),this.el.style.top=this.options.offset+"px",this.el.classList.add("pinned"),"function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pinned")),tthis.options.bottom&&!this.el.classList.contains("pin-bottom")&&(this._removePinClasses(),this.el.classList.add("pin-bottom"),this.el.style.top=this.options.bottom-this.originalOffset+"px","function"==typeof this.options.onPositionChange&&this.options.onPositionChange.call(this,"pin-bottom"))}},{key:"_removePinClasses",value:function(){this.el.classList.remove("pin-top"),this.el.classList.remove("pinned"),this.el.classList.remove("pin-bottom")}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Pushpin}},{key:"_updateElements",value:function(){for(var t in n._pushpins){n._pushpins[t]._updatePosition()}}},{key:"defaults",get:function(){return e}}]),n}();t._pushpins=[],M.Pushpin=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"pushpin","M_Pushpin")}(cash),function(r,s){"use strict";var e={direction:"top",hoverEnabled:!0,toolbarEnabled:!1};r.fn.reverse=[].reverse;var t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_FloatingActionButton=i).options=r.extend({},n.defaults,e),i.isOpen=!1,i.$anchor=i.$el.children("a").first(),i.$menu=i.$el.children("ul").first(),i.$floatingBtns=i.$el.find("ul .btn-floating"),i.$floatingBtnsReverse=i.$el.find("ul .btn-floating").reverse(),i.offsetY=0,i.offsetX=0,i.$el.addClass("direction-"+i.options.direction),"top"===i.options.direction?i.offsetY=40:"right"===i.options.direction?i.offsetX=-40:"bottom"===i.options.direction?i.offsetY=-40:i.offsetX=40,i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_FloatingActionButton=void 0}},{key:"_setupEventHandlers",value:function(){this._handleFABClickBound=this._handleFABClick.bind(this),this._handleOpenBound=this.open.bind(this),this._handleCloseBound=this.close.bind(this),this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.addEventListener("mouseenter",this._handleOpenBound),this.el.addEventListener("mouseleave",this._handleCloseBound)):this.el.addEventListener("click",this._handleFABClickBound)}},{key:"_removeEventHandlers",value:function(){this.options.hoverEnabled&&!this.options.toolbarEnabled?(this.el.removeEventListener("mouseenter",this._handleOpenBound),this.el.removeEventListener("mouseleave",this._handleCloseBound)):this.el.removeEventListener("click",this._handleFABClickBound)}},{key:"_handleFABClick",value:function(){this.isOpen?this.close():this.open()}},{key:"_handleDocumentClick",value:function(t){r(t.target).closest(this.$menu).length||this.close()}},{key:"open",value:function(){this.isOpen||(this.options.toolbarEnabled?this._animateInToolbar():this._animateInFAB(),this.isOpen=!0)}},{key:"close",value:function(){this.isOpen&&(this.options.toolbarEnabled?(window.removeEventListener("scroll",this._handleCloseBound,!0),document.body.removeEventListener("click",this._handleDocumentClickBound,!0),this._animateOutToolbar()):this._animateOutFAB(),this.isOpen=!1)}},{key:"_animateInFAB",value:function(){var e=this;this.$el.addClass("active");var i=0;this.$floatingBtnsReverse.each(function(t){s({targets:t,opacity:1,scale:[.4,1],translateY:[e.offsetY,0],translateX:[e.offsetX,0],duration:275,delay:i,easing:"easeInOutQuad"}),i+=40})}},{key:"_animateOutFAB",value:function(){var e=this;this.$floatingBtnsReverse.each(function(t){s.remove(t),s({targets:t,opacity:0,scale:.4,translateY:e.offsetY,translateX:e.offsetX,duration:175,easing:"easeOutQuad",complete:function(){e.$el.removeClass("active")}})})}},{key:"_animateInToolbar",value:function(){var t,e=this,i=window.innerWidth,n=window.innerHeight,s=this.el.getBoundingClientRect(),o=r('
    '),a=this.$anchor.css("background-color");this.$anchor.append(o),this.offsetX=s.left-i/2+s.width/2,this.offsetY=n-s.bottom,t=i/o[0].clientWidth,this.btnBottom=s.bottom,this.btnLeft=s.left,this.btnWidth=s.width,this.$el.addClass("active"),this.$el.css({"text-align":"center",width:"100%",bottom:0,left:0,transform:"translateX("+this.offsetX+"px)",transition:"none"}),this.$anchor.css({transform:"translateY("+-this.offsetY+"px)",transition:"none"}),o.css({"background-color":a}),setTimeout(function(){e.$el.css({transform:"",transition:"transform .2s cubic-bezier(0.550, 0.085, 0.680, 0.530), background-color 0s linear .2s"}),e.$anchor.css({overflow:"visible",transform:"",transition:"transform .2s"}),setTimeout(function(){e.$el.css({overflow:"hidden","background-color":a}),o.css({transform:"scale("+t+")",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"}),e.$menu.children("li").children("a").css({opacity:1}),e._handleDocumentClickBound=e._handleDocumentClick.bind(e),window.addEventListener("scroll",e._handleCloseBound,!0),document.body.addEventListener("click",e._handleDocumentClickBound,!0)},100)},0)}},{key:"_animateOutToolbar",value:function(){var t=this,e=window.innerWidth,i=window.innerHeight,n=this.$el.find(".fab-backdrop"),s=this.$anchor.css("background-color");this.offsetX=this.btnLeft-e/2+this.btnWidth/2,this.offsetY=i-this.btnBottom,this.$el.removeClass("active"),this.$el.css({"background-color":"transparent",transition:"none"}),this.$anchor.css({transition:"none"}),n.css({transform:"scale(0)","background-color":s}),this.$menu.children("li").children("a").css({opacity:""}),setTimeout(function(){n.remove(),t.$el.css({"text-align":"",width:"",bottom:"",left:"",overflow:"","background-color":"",transform:"translate3d("+-t.offsetX+"px,0,0)"}),t.$anchor.css({overflow:"",transform:"translate3d(0,"+t.offsetY+"px,0)"}),setTimeout(function(){t.$el.css({transform:"translate3d(0,0,0)",transition:"transform .2s"}),t.$anchor.css({transform:"translate3d(0,0,0)",transition:"transform .2s cubic-bezier(0.550, 0.055, 0.675, 0.190)"})},20)},200)}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FloatingActionButton}},{key:"defaults",get:function(){return e}}]),n}();M.FloatingActionButton=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"floatingActionButton","M_FloatingActionButton")}(cash,M.anime),function(g){"use strict";var e={autoClose:!1,format:"mmm dd, yyyy",parse:null,defaultDate:null,setDefaultDate:!1,disableWeekends:!1,disableDayFn:null,firstDay:0,minDate:null,maxDate:null,yearRange:10,minYear:0,maxYear:9999,minMonth:void 0,maxMonth:void 0,startRange:null,endRange:null,isRTL:!1,showMonthAfterYear:!1,showDaysInNextAndPreviousMonths:!1,container:null,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok",previousMonth:"‹",nextMonth:"›",months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],weekdaysAbbrev:["S","M","T","W","T","F","S"]},events:[],onSelect:null,onOpen:null,onClose:null,onDraw:null},t=function(t){function B(t,e){_classCallCheck(this,B);var i=_possibleConstructorReturn(this,(B.__proto__||Object.getPrototypeOf(B)).call(this,B,t,e));(i.el.M_Datepicker=i).options=g.extend({},B.defaults,e),e&&e.hasOwnProperty("i18n")&&"object"==typeof e.i18n&&(i.options.i18n=g.extend({},B.defaults.i18n,e.i18n)),i.options.minDate&&i.options.minDate.setHours(0,0,0,0),i.options.maxDate&&i.options.maxDate.setHours(0,0,0,0),i.id=M.guid(),i._setupVariables(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupEventHandlers(),i.options.defaultDate||(i.options.defaultDate=new Date(Date.parse(i.el.value)));var n=i.options.defaultDate;return B._isDate(n)?i.options.setDefaultDate?(i.setDate(n,!0),i.setInputValue()):i.gotoDate(n):i.gotoDate(new Date),i.isOpen=!1,i}return _inherits(B,Component),_createClass(B,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),g(this.modalEl).remove(),this.destroySelects(),this.el.M_Datepicker=void 0}},{key:"destroySelects",value:function(){var t=this.calendarEl.querySelector(".orig-select-year");t&&M.FormSelect.getInstance(t).destroy();var e=this.calendarEl.querySelector(".orig-select-month");e&&M.FormSelect.getInstance(e).destroy()}},{key:"_insertHTMLIntoDOM",value:function(){this.options.showClearBtn&&(g(this.clearBtn).css({visibility:""}),this.clearBtn.innerHTML=this.options.i18n.clear),this.doneBtn.innerHTML=this.options.i18n.done,this.cancelBtn.innerHTML=this.options.i18n.cancel,this.options.container?this.$modalEl.appendTo(this.options.container):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modalEl.id="modal-"+this.id,this.modal=M.Modal.init(this.modalEl,{onCloseEnd:function(){t.isOpen=!1}})}},{key:"toString",value:function(t){var e=this;return t=t||this.options.format,B._isDate(this.date)?t.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g).map(function(t){return e.formats[t]?e.formats[t]():t}).join(""):""}},{key:"setDate",value:function(t,e){if(!t)return this.date=null,this._renderDateDisplay(),this.draw();if("string"==typeof t&&(t=new Date(Date.parse(t))),B._isDate(t)){var i=this.options.minDate,n=this.options.maxDate;B._isDate(i)&&tn.maxDate||n.disableWeekends&&B._isWeekend(y)||n.disableDayFn&&n.disableDayFn(y),isEmpty:C,isStartRange:x,isEndRange:L,isInRange:T,showDaysInNextAndPreviousMonths:n.showDaysInNextAndPreviousMonths};l.push(this.renderDay($)),7==++_&&(r.push(this.renderRow(l,n.isRTL,m)),_=0,m=!(l=[]))}return this.renderTable(n,r,i)}},{key:"renderDay",value:function(t){var e=[],i="false";if(t.isEmpty){if(!t.showDaysInNextAndPreviousMonths)return'';e.push("is-outside-current-month"),e.push("is-selection-disabled")}return t.isDisabled&&e.push("is-disabled"),t.isToday&&e.push("is-today"),t.isSelected&&(e.push("is-selected"),i="true"),t.hasEvent&&e.push("has-event"),t.isInRange&&e.push("is-inrange"),t.isStartRange&&e.push("is-startrange"),t.isEndRange&&e.push("is-endrange"),'"}},{key:"renderRow",value:function(t,e,i){return''+(e?t.reverse():t).join("")+""}},{key:"renderTable",value:function(t,e,i){return'
    '+this.renderHead(t)+this.renderBody(e)+"
    "}},{key:"renderHead",value:function(t){var e=void 0,i=[];for(e=0;e<7;e++)i.push(''+this.renderDayName(t,e,!0)+"");return""+(t.isRTL?i.reverse():i).join("")+""}},{key:"renderBody",value:function(t){return""+t.join("")+""}},{key:"renderTitle",value:function(t,e,i,n,s,o){var a,r,l=void 0,h=void 0,d=void 0,u=this.options,c=i===u.minYear,p=i===u.maxYear,v='
    ',f=!0,m=!0;for(d=[],l=0;l<12;l++)d.push('");for(a='",g.isArray(u.yearRange)?(l=u.yearRange[0],h=u.yearRange[1]+1):(l=i-u.yearRange,h=1+i+u.yearRange),d=[];l=u.minYear&&d.push('");r='";v+='',v+='
    ',u.showMonthAfterYear?v+=r+a:v+=a+r,v+="
    ",c&&(0===n||u.minMonth>=n)&&(f=!1),p&&(11===n||u.maxMonth<=n)&&(m=!1);return(v+='')+"
    "}},{key:"draw",value:function(t){if(this.isOpen||t){var e,i=this.options,n=i.minYear,s=i.maxYear,o=i.minMonth,a=i.maxMonth,r="";this._y<=n&&(this._y=n,!isNaN(o)&&this._m=s&&(this._y=s,!isNaN(a)&&this._m>a&&(this._m=a)),e="datepicker-title-"+Math.random().toString(36).replace(/[^a-z]+/g,"").substr(0,2);for(var l=0;l<1;l++)this._renderDateDisplay(),r+=this.renderTitle(this,l,this.calendars[l].year,this.calendars[l].month,this.calendars[0].year,e)+this.render(this.calendars[l].year,this.calendars[l].month,e);this.destroySelects(),this.calendarEl.innerHTML=r;var h=this.calendarEl.querySelector(".orig-select-year"),d=this.calendarEl.querySelector(".orig-select-month");M.FormSelect.init(h,{classes:"select-year",dropdownOptions:{container:document.body,constrainWidth:!1}}),M.FormSelect.init(d,{classes:"select-month",dropdownOptions:{container:document.body,constrainWidth:!1}}),h.addEventListener("change",this._handleYearChange.bind(this)),d.addEventListener("change",this._handleMonthChange.bind(this)),"function"==typeof this.options.onDraw&&this.options.onDraw(this)}}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleInputChangeBound=this._handleInputChange.bind(this),this._handleCalendarClickBound=this._handleCalendarClick.bind(this),this._finishSelectionBound=this._finishSelection.bind(this),this._handleMonthChange=this._handleMonthChange.bind(this),this._closeBound=this.close.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.el.addEventListener("change",this._handleInputChangeBound),this.calendarEl.addEventListener("click",this._handleCalendarClickBound),this.doneBtn.addEventListener("click",this._finishSelectionBound),this.cancelBtn.addEventListener("click",this._closeBound),this.options.showClearBtn&&(this._handleClearClickBound=this._handleClearClick.bind(this),this.clearBtn.addEventListener("click",this._handleClearClickBound))}},{key:"_setupVariables",value:function(){var e=this;this.$modalEl=g(B._template),this.modalEl=this.$modalEl[0],this.calendarEl=this.modalEl.querySelector(".datepicker-calendar"),this.yearTextEl=this.modalEl.querySelector(".year-text"),this.dateTextEl=this.modalEl.querySelector(".date-text"),this.options.showClearBtn&&(this.clearBtn=this.modalEl.querySelector(".datepicker-clear")),this.doneBtn=this.modalEl.querySelector(".datepicker-done"),this.cancelBtn=this.modalEl.querySelector(".datepicker-cancel"),this.formats={d:function(){return e.date.getDate()},dd:function(){var t=e.date.getDate();return(t<10?"0":"")+t},ddd:function(){return e.options.i18n.weekdaysShort[e.date.getDay()]},dddd:function(){return e.options.i18n.weekdays[e.date.getDay()]},m:function(){return e.date.getMonth()+1},mm:function(){var t=e.date.getMonth()+1;return(t<10?"0":"")+t},mmm:function(){return e.options.i18n.monthsShort[e.date.getMonth()]},mmmm:function(){return e.options.i18n.months[e.date.getMonth()]},yy:function(){return(""+e.date.getFullYear()).slice(2)},yyyy:function(){return e.date.getFullYear()}}}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound),this.el.removeEventListener("change",this._handleInputChangeBound),this.calendarEl.removeEventListener("click",this._handleCalendarClickBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleCalendarClick",value:function(t){if(this.isOpen){var e=g(t.target);e.hasClass("is-disabled")||(!e.hasClass("datepicker-day-button")||e.hasClass("is-empty")||e.parent().hasClass("is-disabled")?e.closest(".month-prev").length?this.prevMonth():e.closest(".month-next").length&&this.nextMonth():(this.setDate(new Date(t.target.getAttribute("data-year"),t.target.getAttribute("data-month"),t.target.getAttribute("data-day"))),this.options.autoClose&&this._finishSelection()))}}},{key:"_handleClearClick",value:function(){this.date=null,this.setInputValue(),this.close()}},{key:"_handleMonthChange",value:function(t){this.gotoMonth(t.target.value)}},{key:"_handleYearChange",value:function(t){this.gotoYear(t.target.value)}},{key:"gotoMonth",value:function(t){isNaN(t)||(this.calendars[0].month=parseInt(t,10),this.adjustCalendars())}},{key:"gotoYear",value:function(t){isNaN(t)||(this.calendars[0].year=parseInt(t,10),this.adjustCalendars())}},{key:"_handleInputChange",value:function(t){var e=void 0;t.firedBy!==this&&(e=this.options.parse?this.options.parse(this.el.value,this.options.format):new Date(Date.parse(this.el.value)),B._isDate(e)&&this.setDate(e))}},{key:"renderDayName",value:function(t,e,i){for(e+=t.firstDay;7<=e;)e-=7;return i?t.i18n.weekdaysAbbrev[e]:t.i18n.weekdays[e]}},{key:"_finishSelection",value:function(){this.setInputValue(),this.close()}},{key:"open",value:function(){if(!this.isOpen)return this.isOpen=!0,"function"==typeof this.options.onOpen&&this.options.onOpen.call(this),this.draw(),this.modal.open(),this}},{key:"close",value:function(){if(this.isOpen)return this.isOpen=!1,"function"==typeof this.options.onClose&&this.options.onClose.call(this),this.modal.close(),this}}],[{key:"init",value:function(t,e){return _get(B.__proto__||Object.getPrototypeOf(B),"init",this).call(this,this,t,e)}},{key:"_isDate",value:function(t){return/Date/.test(Object.prototype.toString.call(t))&&!isNaN(t.getTime())}},{key:"_isWeekend",value:function(t){var e=t.getDay();return 0===e||6===e}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"_getDaysInMonth",value:function(t,e){return[31,B._isLeapYear(t)?29:28,31,30,31,30,31,31,30,31,30,31][e]}},{key:"_isLeapYear",value:function(t){return t%4==0&&t%100!=0||t%400==0}},{key:"_compareDates",value:function(t,e){return t.getTime()===e.getTime()}},{key:"_setToStartOfDay",value:function(t){B._isDate(t)&&t.setHours(0,0,0,0)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Datepicker}},{key:"defaults",get:function(){return e}}]),B}();t._template=['"].join(""),M.Datepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"datepicker","M_Datepicker")}(cash),function(h){"use strict";var e={dialRadius:135,outerRadius:105,innerRadius:70,tickRadius:20,duration:350,container:null,defaultTime:"now",fromNow:0,showClearBtn:!1,i18n:{cancel:"Cancel",clear:"Clear",done:"Ok"},autoClose:!1,twelveHour:!0,vibrate:!0,onOpenStart:null,onOpenEnd:null,onCloseStart:null,onCloseEnd:null,onSelect:null},t=function(t){function f(t,e){_classCallCheck(this,f);var i=_possibleConstructorReturn(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,f,t,e));return(i.el.M_Timepicker=i).options=h.extend({},f.defaults,e),i.id=M.guid(),i._insertHTMLIntoDOM(),i._setupModal(),i._setupVariables(),i._setupEventHandlers(),i._clockSetup(),i._pickerSetup(),i}return _inherits(f,Component),_createClass(f,[{key:"destroy",value:function(){this._removeEventHandlers(),this.modal.destroy(),h(this.modalEl).remove(),this.el.M_Timepicker=void 0}},{key:"_setupEventHandlers",value:function(){this._handleInputKeydownBound=this._handleInputKeydown.bind(this),this._handleInputClickBound=this._handleInputClick.bind(this),this._handleClockClickStartBound=this._handleClockClickStart.bind(this),this._handleDocumentClickMoveBound=this._handleDocumentClickMove.bind(this),this._handleDocumentClickEndBound=this._handleDocumentClickEnd.bind(this),this.el.addEventListener("click",this._handleInputClickBound),this.el.addEventListener("keydown",this._handleInputKeydownBound),this.plate.addEventListener("mousedown",this._handleClockClickStartBound),this.plate.addEventListener("touchstart",this._handleClockClickStartBound),h(this.spanHours).on("click",this.showView.bind(this,"hours")),h(this.spanMinutes).on("click",this.showView.bind(this,"minutes"))}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleInputClickBound),this.el.removeEventListener("keydown",this._handleInputKeydownBound)}},{key:"_handleInputClick",value:function(){this.open()}},{key:"_handleInputKeydown",value:function(t){t.which===M.keys.ENTER&&(t.preventDefault(),this.open())}},{key:"_handleClockClickStart",value:function(t){t.preventDefault();var e=this.plate.getBoundingClientRect(),i=e.left,n=e.top;this.x0=i+this.options.dialRadius,this.y0=n+this.options.dialRadius,this.moved=!1;var s=f._Pos(t);this.dx=s.x-this.x0,this.dy=s.y-this.y0,this.setHand(this.dx,this.dy,!1),document.addEventListener("mousemove",this._handleDocumentClickMoveBound),document.addEventListener("touchmove",this._handleDocumentClickMoveBound),document.addEventListener("mouseup",this._handleDocumentClickEndBound),document.addEventListener("touchend",this._handleDocumentClickEndBound)}},{key:"_handleDocumentClickMove",value:function(t){t.preventDefault();var e=f._Pos(t),i=e.x-this.x0,n=e.y-this.y0;this.moved=!0,this.setHand(i,n,!1,!0)}},{key:"_handleDocumentClickEnd",value:function(t){var e=this;t.preventDefault(),document.removeEventListener("mouseup",this._handleDocumentClickEndBound),document.removeEventListener("touchend",this._handleDocumentClickEndBound);var i=f._Pos(t),n=i.x-this.x0,s=i.y-this.y0;this.moved&&n===this.dx&&s===this.dy&&this.setHand(n,s),"hours"===this.currentView?this.showView("minutes",this.options.duration/2):this.options.autoClose&&(h(this.minutesView).addClass("timepicker-dial-out"),setTimeout(function(){e.done()},this.options.duration/2)),"function"==typeof this.options.onSelect&&this.options.onSelect.call(this,this.hours,this.minutes),document.removeEventListener("mousemove",this._handleDocumentClickMoveBound),document.removeEventListener("touchmove",this._handleDocumentClickMoveBound)}},{key:"_insertHTMLIntoDOM",value:function(){this.$modalEl=h(f._template),this.modalEl=this.$modalEl[0],this.modalEl.id="modal-"+this.id;var t=document.querySelector(this.options.container);this.options.container&&t?this.$modalEl.appendTo(t):this.$modalEl.insertBefore(this.el)}},{key:"_setupModal",value:function(){var t=this;this.modal=M.Modal.init(this.modalEl,{onOpenStart:this.options.onOpenStart,onOpenEnd:this.options.onOpenEnd,onCloseStart:this.options.onCloseStart,onCloseEnd:function(){"function"==typeof t.options.onCloseEnd&&t.options.onCloseEnd.call(t),t.isOpen=!1}})}},{key:"_setupVariables",value:function(){this.currentView="hours",this.vibrate=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,this._canvas=this.modalEl.querySelector(".timepicker-canvas"),this.plate=this.modalEl.querySelector(".timepicker-plate"),this.hoursView=this.modalEl.querySelector(".timepicker-hours"),this.minutesView=this.modalEl.querySelector(".timepicker-minutes"),this.spanHours=this.modalEl.querySelector(".timepicker-span-hours"),this.spanMinutes=this.modalEl.querySelector(".timepicker-span-minutes"),this.spanAmPm=this.modalEl.querySelector(".timepicker-span-am-pm"),this.footer=this.modalEl.querySelector(".timepicker-footer"),this.amOrPm="PM"}},{key:"_pickerSetup",value:function(){var t=h('").appendTo(this.footer).on("click",this.clear.bind(this));this.options.showClearBtn&&t.css({visibility:""});var e=h('
    ');h('").appendTo(e).on("click",this.close.bind(this)),h('").appendTo(e).on("click",this.done.bind(this)),e.appendTo(this.footer)}},{key:"_clockSetup",value:function(){this.options.twelveHour&&(this.$amBtn=h('
    AM
    '),this.$pmBtn=h('
    PM
    '),this.$amBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm),this.$pmBtn.on("click",this._handleAmPmClick.bind(this)).appendTo(this.spanAmPm)),this._buildHoursView(),this._buildMinutesView(),this._buildSVGClock()}},{key:"_buildSVGClock",value:function(){var t=this.options.dialRadius,e=this.options.tickRadius,i=2*t,n=f._createSVGEl("svg");n.setAttribute("class","timepicker-svg"),n.setAttribute("width",i),n.setAttribute("height",i);var s=f._createSVGEl("g");s.setAttribute("transform","translate("+t+","+t+")");var o=f._createSVGEl("circle");o.setAttribute("class","timepicker-canvas-bearing"),o.setAttribute("cx",0),o.setAttribute("cy",0),o.setAttribute("r",4);var a=f._createSVGEl("line");a.setAttribute("x1",0),a.setAttribute("y1",0);var r=f._createSVGEl("circle");r.setAttribute("class","timepicker-canvas-bg"),r.setAttribute("r",e),s.appendChild(a),s.appendChild(r),s.appendChild(o),n.appendChild(s),this._canvas.appendChild(n),this.hand=a,this.bg=r,this.bearing=o,this.g=s}},{key:"_buildHoursView",value:function(){var t=h('
    ');if(this.options.twelveHour)for(var e=1;e<13;e+=1){var i=t.clone(),n=e/6*Math.PI,s=this.options.outerRadius;i.css({left:this.options.dialRadius+Math.sin(n)*s-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*s-this.options.tickRadius+"px"}),i.html(0===e?"00":e),this.hoursView.appendChild(i[0])}else for(var o=0;o<24;o+=1){var a=t.clone(),r=o/6*Math.PI,l=0
    '),e=0;e<60;e+=5){var i=t.clone(),n=e/30*Math.PI;i.css({left:this.options.dialRadius+Math.sin(n)*this.options.outerRadius-this.options.tickRadius+"px",top:this.options.dialRadius-Math.cos(n)*this.options.outerRadius-this.options.tickRadius+"px"}),i.html(f._addLeadingZero(e)),this.minutesView.appendChild(i[0])}}},{key:"_handleAmPmClick",value:function(t){var e=h(t.target);this.amOrPm=e.hasClass("am-btn")?"AM":"PM",this._updateAmPmView()}},{key:"_updateAmPmView",value:function(){this.options.twelveHour&&(this.$amBtn.toggleClass("text-primary","AM"===this.amOrPm),this.$pmBtn.toggleClass("text-primary","PM"===this.amOrPm))}},{key:"_updateTimeFromInput",value:function(){var t=((this.el.value||this.options.defaultTime||"")+"").split(":");if(this.options.twelveHour&&void 0!==t[1]&&(0','",""].join(""),M.Timepicker=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"timepicker","M_Timepicker")}(cash),function(s){"use strict";var e={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_CharacterCounter=i).options=s.extend({},n.defaults,e),i.isInvalid=!1,i.isValidLength=!1,i._setupCounter(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.CharacterCounter=void 0,this._removeCounter()}},{key:"_setupEventHandlers",value:function(){this._handleUpdateCounterBound=this.updateCounter.bind(this),this.el.addEventListener("focus",this._handleUpdateCounterBound,!0),this.el.addEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("focus",this._handleUpdateCounterBound,!0),this.el.removeEventListener("input",this._handleUpdateCounterBound,!0)}},{key:"_setupCounter",value:function(){this.counterEl=document.createElement("span"),s(this.counterEl).addClass("character-counter").css({float:"right","font-size":"12px",height:1}),this.$el.parent().append(this.counterEl)}},{key:"_removeCounter",value:function(){s(this.counterEl).remove()}},{key:"updateCounter",value:function(){var t=+this.$el.attr("data-length"),e=this.el.value.length;this.isValidLength=e<=t;var i=e;t&&(i+="/"+t,this._validateInput()),s(this.counterEl).html(i)}},{key:"_validateInput",value:function(){this.isValidLength&&this.isInvalid?(this.isInvalid=!1,this.$el.removeClass("invalid")):this.isValidLength||this.isInvalid||(this.isInvalid=!0,this.$el.removeClass("valid"),this.$el.addClass("invalid"))}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_CharacterCounter}},{key:"defaults",get:function(){return e}}]),n}();M.CharacterCounter=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"characterCounter","M_CharacterCounter")}(cash),function(b){"use strict";var e={duration:200,dist:-100,shift:0,padding:0,numVisible:5,fullWidth:!1,indicators:!1,noWrap:!1,onCycleTo:null},t=function(t){function i(t,e){_classCallCheck(this,i);var n=_possibleConstructorReturn(this,(i.__proto__||Object.getPrototypeOf(i)).call(this,i,t,e));return(n.el.M_Carousel=n).options=b.extend({},i.defaults,e),n.hasMultipleSlides=1'),n.$el.find(".carousel-item").each(function(t,e){if(n.images.push(t),n.showIndicators){var i=b('
  • ');0===e&&i[0].classList.add("active"),n.$indicators.append(i)}}),n.showIndicators&&n.$el.append(n.$indicators),n.count=n.images.length,n.options.numVisible=Math.min(n.count,n.options.numVisible),n.xform="transform",["webkit","Moz","O","ms"].every(function(t){var e=t+"Transform";return void 0===document.body.style[e]||(n.xform=e,!1)}),n._setupEventHandlers(),n._scroll(n.offset),n}return _inherits(i,Component),_createClass(i,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.M_Carousel=void 0}},{key:"_setupEventHandlers",value:function(){var i=this;this._handleCarouselTapBound=this._handleCarouselTap.bind(this),this._handleCarouselDragBound=this._handleCarouselDrag.bind(this),this._handleCarouselReleaseBound=this._handleCarouselRelease.bind(this),this._handleCarouselClickBound=this._handleCarouselClick.bind(this),void 0!==window.ontouchstart&&(this.el.addEventListener("touchstart",this._handleCarouselTapBound),this.el.addEventListener("touchmove",this._handleCarouselDragBound),this.el.addEventListener("touchend",this._handleCarouselReleaseBound)),this.el.addEventListener("mousedown",this._handleCarouselTapBound),this.el.addEventListener("mousemove",this._handleCarouselDragBound),this.el.addEventListener("mouseup",this._handleCarouselReleaseBound),this.el.addEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.addEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&(this._handleIndicatorClickBound=this._handleIndicatorClick.bind(this),this.$indicators.find(".indicator-item").each(function(t,e){t.addEventListener("click",i._handleIndicatorClickBound)}));var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){var i=this;void 0!==window.ontouchstart&&(this.el.removeEventListener("touchstart",this._handleCarouselTapBound),this.el.removeEventListener("touchmove",this._handleCarouselDragBound),this.el.removeEventListener("touchend",this._handleCarouselReleaseBound)),this.el.removeEventListener("mousedown",this._handleCarouselTapBound),this.el.removeEventListener("mousemove",this._handleCarouselDragBound),this.el.removeEventListener("mouseup",this._handleCarouselReleaseBound),this.el.removeEventListener("mouseleave",this._handleCarouselReleaseBound),this.el.removeEventListener("click",this._handleCarouselClickBound),this.showIndicators&&this.$indicators&&this.$indicators.find(".indicator-item").each(function(t,e){t.removeEventListener("click",i._handleIndicatorClickBound)}),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleCarouselTap",value:function(t){"mousedown"===t.type&&b(t.target).is("img")&&t.preventDefault(),this.pressed=!0,this.dragged=!1,this.verticalDragged=!1,this.reference=this._xpos(t),this.referenceY=this._ypos(t),this.velocity=this.amplitude=0,this.frame=this.offset,this.timestamp=Date.now(),clearInterval(this.ticker),this.ticker=setInterval(this._trackBound,100)}},{key:"_handleCarouselDrag",value:function(t){var e=void 0,i=void 0,n=void 0;if(this.pressed)if(e=this._xpos(t),i=this._ypos(t),n=this.reference-e,Math.abs(this.referenceY-i)<30&&!this.verticalDragged)(2=this.dim*(this.count-1)?this.target=this.dim*(this.count-1):this.target<0&&(this.target=0)),this.amplitude=this.target-this.offset,this.timestamp=Date.now(),requestAnimationFrame(this._autoScrollBound),this.dragged&&(t.preventDefault(),t.stopPropagation()),!1}},{key:"_handleCarouselClick",value:function(t){if(this.dragged)return t.preventDefault(),t.stopPropagation(),!1;if(!this.options.fullWidth){var e=b(t.target).closest(".carousel-item").index();0!==this._wrap(this.center)-e&&(t.preventDefault(),t.stopPropagation()),this._cycleTo(e)}}},{key:"_handleIndicatorClick",value:function(t){t.stopPropagation();var e=b(t.target).closest(".indicator-item");e.length&&this._cycleTo(e.index())}},{key:"_handleResize",value:function(t){this.options.fullWidth?(this.itemWidth=this.$el.find(".carousel-item").first().innerWidth(),this.imageHeight=this.$el.find(".carousel-item.active").height(),this.dim=2*this.itemWidth+this.options.padding,this.offset=2*this.center*this.itemWidth,this.target=this.offset,this._setCarouselHeight(!0)):this._scroll()}},{key:"_setCarouselHeight",value:function(t){var i=this,e=this.$el.find(".carousel-item.active").length?this.$el.find(".carousel-item.active").first():this.$el.find(".carousel-item").first(),n=e.find("img").first();if(n.length)if(n[0].complete){var s=n.height();if(0=this.count?t%this.count:t<0?this._wrap(this.count+t%this.count):t}},{key:"_track",value:function(){var t,e,i,n;e=(t=Date.now())-this.timestamp,this.timestamp=t,i=this.offset-this.frame,this.frame=this.offset,n=1e3*i/(1+e),this.velocity=.8*n+.2*this.velocity}},{key:"_autoScroll",value:function(){var t=void 0,e=void 0;this.amplitude&&(t=Date.now()-this.timestamp,2<(e=this.amplitude*Math.exp(-t/this.options.duration))||e<-2?(this._scroll(this.target-e),requestAnimationFrame(this._autoScrollBound)):this._scroll(this.target))}},{key:"_scroll",value:function(t){var e=this;this.$el.hasClass("scrolling")||this.el.classList.add("scrolling"),null!=this.scrollingTimeout&&window.clearTimeout(this.scrollingTimeout),this.scrollingTimeout=window.setTimeout(function(){e.$el.removeClass("scrolling")},this.options.duration);var i,n,s,o,a=void 0,r=void 0,l=void 0,h=void 0,d=void 0,u=void 0,c=this.center,p=1/this.options.numVisible;if(this.offset="number"==typeof t?t:this.offset,this.center=Math.floor((this.offset+this.dim/2)/this.dim),o=-(s=(n=this.offset-this.center*this.dim)<0?1:-1)*n*2/this.dim,i=this.count>>1,this.options.fullWidth?(l="translateX(0)",u=1):(l="translateX("+(this.el.clientWidth-this.itemWidth)/2+"px) ",l+="translateY("+(this.el.clientHeight-this.itemHeight)/2+"px)",u=1-p*o),this.showIndicators){var v=this.center%this.count,f=this.$indicators.find(".indicator-item.active");f.index()!==v&&(f.removeClass("active"),this.$indicators.find(".indicator-item").eq(v)[0].classList.add("active"))}if(!this.noWrap||0<=this.center&&this.center=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"prev",value:function(t){(void 0===t||isNaN(t))&&(t=1);var e=this.center-t;if(e>=this.count||e<0){if(this.noWrap)return;e=this._wrap(e)}this._cycleTo(e)}},{key:"set",value:function(t,e){if((void 0===t||isNaN(t))&&(t=0),t>this.count||t<0){if(this.noWrap)return;t=this._wrap(t)}this._cycleTo(t,e)}}],[{key:"init",value:function(t,e){return _get(i.__proto__||Object.getPrototypeOf(i),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Carousel}},{key:"defaults",get:function(){return e}}]),i}();M.Carousel=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"carousel","M_Carousel")}(cash),function(S){"use strict";var e={onOpen:void 0,onClose:void 0},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_TapTarget=i).options=S.extend({},n.defaults,e),i.isOpen=!1,i.$origin=S("#"+i.$el.attr("data-target")),i._setup(),i._calculatePositioning(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this.el.TapTarget=void 0}},{key:"_setupEventHandlers",value:function(){this._handleDocumentClickBound=this._handleDocumentClick.bind(this),this._handleTargetClickBound=this._handleTargetClick.bind(this),this._handleOriginClickBound=this._handleOriginClick.bind(this),this.el.addEventListener("click",this._handleTargetClickBound),this.originEl.addEventListener("click",this._handleOriginClickBound);var t=M.throttle(this._handleResize,200);this._handleThrottledResizeBound=t.bind(this),window.addEventListener("resize",this._handleThrottledResizeBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("click",this._handleTargetClickBound),this.originEl.removeEventListener("click",this._handleOriginClickBound),window.removeEventListener("resize",this._handleThrottledResizeBound)}},{key:"_handleTargetClick",value:function(t){this.open()}},{key:"_handleOriginClick",value:function(t){this.close()}},{key:"_handleResize",value:function(t){this._calculatePositioning()}},{key:"_handleDocumentClick",value:function(t){S(t.target).closest(".tap-target-wrapper").length||(this.close(),t.preventDefault(),t.stopPropagation())}},{key:"_setup",value:function(){this.wrapper=this.$el.parent()[0],this.waveEl=S(this.wrapper).find(".tap-target-wave")[0],this.originEl=S(this.wrapper).find(".tap-target-origin")[0],this.contentEl=this.$el.find(".tap-target-content")[0],S(this.wrapper).hasClass(".tap-target-wrapper")||(this.wrapper=document.createElement("div"),this.wrapper.classList.add("tap-target-wrapper"),this.$el.before(S(this.wrapper)),this.wrapper.append(this.el)),this.contentEl||(this.contentEl=document.createElement("div"),this.contentEl.classList.add("tap-target-content"),this.$el.append(this.contentEl)),this.waveEl||(this.waveEl=document.createElement("div"),this.waveEl.classList.add("tap-target-wave"),this.originEl||(this.originEl=this.$origin.clone(!0,!0),this.originEl.addClass("tap-target-origin"),this.originEl.removeAttr("id"),this.originEl.removeAttr("style"),this.originEl=this.originEl[0],this.waveEl.append(this.originEl)),this.wrapper.append(this.waveEl))}},{key:"_calculatePositioning",value:function(){var t="fixed"===this.$origin.css("position");if(!t)for(var e=this.$origin.parents(),i=0;i'+t.getAttribute("label")+"")[0]),i.each(function(t){var e=n._appendOptionWithIcon(n.$el,t,"optgroup-option");n._addOptionToValueDict(t,e)})}}),this.$el.after(this.dropdownOptions),this.input=document.createElement("input"),d(this.input).addClass("select-dropdown dropdown-trigger"),this.input.setAttribute("type","text"),this.input.setAttribute("readonly","true"),this.input.setAttribute("data-target",this.dropdownOptions.id),this.el.disabled&&d(this.input).prop("disabled","true"),this.$el.before(this.input),this._setValueToInput();var t=d('');if(this.$el.before(t[0]),!this.el.disabled){var e=d.extend({},this.options.dropdownOptions);e.onOpenEnd=function(t){var e=d(n.dropdownOptions).find(".selected").first();if(e.length&&(M.keyDown=!0,n.dropdown.focusedIndex=e.index(),n.dropdown._focusFocusedItem(),M.keyDown=!1,n.dropdown.isScrollable)){var i=e[0].getBoundingClientRect().top-n.dropdownOptions.getBoundingClientRect().top;i-=n.dropdownOptions.clientHeight/2,n.dropdownOptions.scrollTop=i}},this.isMultiple&&(e.closeOnClick=!1),this.dropdown=M.Dropdown.init(this.input,e)}this._setSelectedStates()}},{key:"_addOptionToValueDict",value:function(t,e){var i=Object.keys(this._valueDict).length,n=this.dropdownOptions.id+i,s={};e.id=n,s.el=t,s.optionEl=e,this._valueDict[n]=s}},{key:"_removeDropdown",value:function(){d(this.wrapper).find(".caret").remove(),d(this.input).remove(),d(this.dropdownOptions).remove(),d(this.wrapper).before(this.$el),d(this.wrapper).remove()}},{key:"_appendOptionWithIcon",value:function(t,e,i){var n=e.disabled?"disabled ":"",s="optgroup-option"===i?"optgroup-option ":"",o=this.isMultiple?'":e.innerHTML,a=d("
  • "),r=d("");r.html(o),a.addClass(n+" "+s),a.append(r);var l=e.getAttribute("data-icon");if(l){var h=d('');a.prepend(h)}return d(this.dropdownOptions).append(a[0]),a[0]}},{key:"_toggleEntryFromArray",value:function(t){var e=!this._keysSelected.hasOwnProperty(t),i=d(this._valueDict[t].optionEl);return e?this._keysSelected[t]=!0:delete this._keysSelected[t],i.toggleClass("selected",e),i.find('input[type="checkbox"]').prop("checked",e),i.prop("selected",e),e}},{key:"_setValueToInput",value:function(){var i=[];if(this.$el.find("option").each(function(t){if(d(t).prop("selected")){var e=d(t).text();i.push(e)}}),!i.length){var t=this.$el.find("option:disabled").eq(0);t.length&&""===t[0].value&&i.push(t.text())}this.input.value=i.join(", ")}},{key:"_setSelectedStates",value:function(){for(var t in this._keysSelected={},this._valueDict){var e=this._valueDict[t],i=d(e.el).prop("selected");d(e.optionEl).find('input[type="checkbox"]').prop("checked",i),i?(this._activateOption(d(this.dropdownOptions),d(e.optionEl)),this._keysSelected[t]=!0):d(e.optionEl).removeClass("selected")}}},{key:"_activateOption",value:function(t,e){e&&(this.isMultiple||t.find("li.selected").removeClass("selected"),d(e).addClass("selected"))}},{key:"getSelectedValues",value:function(){var t=[];for(var e in this._keysSelected)t.push(this._valueDict[e].el.value);return t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_FormSelect}},{key:"defaults",get:function(){return e}}]),n}();M.FormSelect=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"formSelect","M_FormSelect")}(cash),function(s,e){"use strict";var i={},t=function(t){function n(t,e){_classCallCheck(this,n);var i=_possibleConstructorReturn(this,(n.__proto__||Object.getPrototypeOf(n)).call(this,n,t,e));return(i.el.M_Range=i).options=s.extend({},n.defaults,e),i._mousedown=!1,i._setupThumb(),i._setupEventHandlers(),i}return _inherits(n,Component),_createClass(n,[{key:"destroy",value:function(){this._removeEventHandlers(),this._removeThumb(),this.el.M_Range=void 0}},{key:"_setupEventHandlers",value:function(){this._handleRangeChangeBound=this._handleRangeChange.bind(this),this._handleRangeMousedownTouchstartBound=this._handleRangeMousedownTouchstart.bind(this),this._handleRangeInputMousemoveTouchmoveBound=this._handleRangeInputMousemoveTouchmove.bind(this),this._handleRangeMouseupTouchendBound=this._handleRangeMouseupTouchend.bind(this),this._handleRangeBlurMouseoutTouchleaveBound=this._handleRangeBlurMouseoutTouchleave.bind(this),this.el.addEventListener("change",this._handleRangeChangeBound),this.el.addEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.addEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.addEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.addEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.addEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.addEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_removeEventHandlers",value:function(){this.el.removeEventListener("change",this._handleRangeChangeBound),this.el.removeEventListener("mousedown",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("touchstart",this._handleRangeMousedownTouchstartBound),this.el.removeEventListener("input",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mousemove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("touchmove",this._handleRangeInputMousemoveTouchmoveBound),this.el.removeEventListener("mouseup",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("touchend",this._handleRangeMouseupTouchendBound),this.el.removeEventListener("blur",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("mouseout",this._handleRangeBlurMouseoutTouchleaveBound),this.el.removeEventListener("touchleave",this._handleRangeBlurMouseoutTouchleaveBound)}},{key:"_handleRangeChange",value:function(){s(this.value).html(this.$el.val()),s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px")}},{key:"_handleRangeMousedownTouchstart",value:function(t){if(s(this.value).html(this.$el.val()),this._mousedown=!0,this.$el.addClass("active"),s(this.thumb).hasClass("active")||this._showRangeBubble(),"input"!==t.type){var e=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",e+"px")}}},{key:"_handleRangeInputMousemoveTouchmove",value:function(){if(this._mousedown){s(this.thumb).hasClass("active")||this._showRangeBubble();var t=this._calcRangeOffset();s(this.thumb).addClass("active").css("left",t+"px"),s(this.value).html(this.$el.val())}}},{key:"_handleRangeMouseupTouchend",value:function(){this._mousedown=!1,this.$el.removeClass("active")}},{key:"_handleRangeBlurMouseoutTouchleave",value:function(){if(!this._mousedown){var t=7+parseInt(this.$el.css("padding-left"))+"px";s(this.thumb).hasClass("active")&&(e.remove(this.thumb),e({targets:this.thumb,height:0,width:0,top:10,easing:"easeOutQuad",marginLeft:t,duration:100})),s(this.thumb).removeClass("active")}}},{key:"_setupThumb",value:function(){this.thumb=document.createElement("span"),this.value=document.createElement("span"),s(this.thumb).addClass("thumb"),s(this.value).addClass("value"),s(this.thumb).append(this.value),this.$el.after(this.thumb)}},{key:"_removeThumb",value:function(){s(this.thumb).remove()}},{key:"_showRangeBubble",value:function(){var t=-7+parseInt(s(this.thumb).parent().css("padding-left"))+"px";e.remove(this.thumb),e({targets:this.thumb,height:30,width:30,top:-30,marginLeft:t,duration:300,easing:"easeOutQuint"})}},{key:"_calcRangeOffset",value:function(){var t=this.$el.width()-15,e=parseFloat(this.$el.attr("max"))||100,i=parseFloat(this.$el.attr("min"))||0;return(parseFloat(this.$el.val())-i)/(e-i)*t}}],[{key:"init",value:function(t,e){return _get(n.__proto__||Object.getPrototypeOf(n),"init",this).call(this,this,t,e)}},{key:"getInstance",value:function(t){return(t.jquery?t[0]:t).M_Range}},{key:"defaults",get:function(){return i}}]),n}();M.Range=t,M.jQueryLoaded&&M.initializeJqueryWrapper(t,"range","M_Range"),t.init(s("input[type=range]"))}(cash,M.anime); diff --git a/esphome/dashboard/templates/index.html b/esphome/dashboard/templates/index.html deleted file mode 100644 index b10901f5d4..0000000000 --- a/esphome/dashboard/templates/index.html +++ /dev/null @@ -1,678 +0,0 @@ - - - - - - - Dashboard - ESPHome - - - - - - - - - - - - - - - {% if streamer_mode %} - - {% end %} - - - - - - -
    -
    - -
    - - {% for i, entry in enumerate(entries) %} -
    -
    - - {{ escape(entry.name) }} - - more_vert - - {% if 'web_server' in entry.loaded_integrations %} - launch - {% end %} - - {% if entry.update_available %} - system_update - {% end %} - - -
    - Filename: - - {{ escape(entry.filename) }} - -
    - - {% if entry.comment %} -
    - {{ escape(entry.comment) }} -
    - {% end %} - -
    -
    - Edit - Validate - Upload - Logs -
    - -
    - {% end %} - -
    - -
    - - {% if len(entries) == 0 %} -
    -
    Welcome to ESPHome
    -

    It looks like you don't yet have any Nodes configured.

    -

    Click on the pulsating button at the bottom right of the page to add a Node.

    -
    - - - {% end %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - {% if begin and len(entries) == 1 %} - - {% end %} - - - - diff --git a/esphome/dashboard/templates/login.html b/esphome/dashboard/templates/login.html deleted file mode 100644 index 14116484af..0000000000 --- a/esphome/dashboard/templates/login.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - Login - ESPHome - - - - - - - - - - - - -
    -
    -
    -
    -
    -
    -
    - - v{{ version }} - Dashboard Login -

    - {% if hassio %} - Login by entering your Home Assistant login credentials. - {% else %} - Login by entering your ESPHome login credentials. - {% end %} -

    - - {% if error is not None %} -
    - Error! - {{ escape(error) }} -
    - - - {% end %} - -
    - {% if has_username or hassio %} -
    -
    - person - - -
    -
    - {% end %} - -
    -
    - lock - - -
    -
    -
    -
    - -
    - -
    -
    -
    -
    -
    - - - -
    -
    - - - diff --git a/esphome/wizard.py b/esphome/wizard.py index 7d875b7dd2..0d912e4bbf 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -49,17 +49,6 @@ BASE_CONFIG = """esphome: platform: {platform} board: {board} -wifi: - ssid: "{ssid}" - password: "{psk}" - - # Enable fallback hotspot (captive portal) in case wifi connection fails - ap: - ssid: "{fallback_name}" - password: "{fallback_psk}" - -captive_portal: - # Enable logging logger: @@ -83,12 +72,43 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) - if kwargs["password"]: - config += ' password: "{0}"\n\nota:\n password: "{0}"\n'.format( - kwargs["password"] + # Configure API + if "password" in kwargs: + config += ' password: "{0}"\n'.format(kwargs["password"]) + + # Configure OTA + config += "\nota:\n" + if "ota_password" in kwargs: + config += ' password: "{0}"'.format(kwargs["ota_password"]) + elif "password" in kwargs: + config += ' password: "{0}"'.format(kwargs["password"]) + + # Configuring wifi + config += "\n\nwifi:\n" + + if "ssid" in kwargs: + config += """ ssid: "{ssid}" + password: "{psk}" +""".format( + **kwargs ) else: - config += "\nota:\n" + config += """ # ssid: "My SSID" + # password: "mypassword" + + networks: +""" + + config += """ + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "{fallback_name}" + password: "{fallback_psk}" + +captive_portal: +""".format( + **kwargs + ) return config @@ -97,9 +117,9 @@ def wizard_write(path, **kwargs): name = kwargs["name"] board = kwargs["board"] - kwargs["ssid"] = sanitize_double_quotes(kwargs["ssid"]) - kwargs["psk"] = sanitize_double_quotes(kwargs["psk"]) - kwargs["password"] = sanitize_double_quotes(kwargs["password"]) + for key in ("ssid", "psk", "password", "ota_password"): + if key in kwargs: + kwargs[key] = sanitize_double_quotes(kwargs[key]) if "platform" not in kwargs: kwargs["platform"] = "ESP8266" if board in ESP8266_BOARD_PINS else "ESP32" diff --git a/requirements.txt b/requirements.txt index 5915c2963f..e7f69865da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 +esphome-dashboard==20210611.0 From f599c36272c448192ddfad0eb1b68c9b04dd2d8a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 11:03:09 +1200 Subject: [PATCH 009/105] Bump version to v1.19.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 30df9abe17..f7ad39b95d 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b2" +PATCH_VERSION = "0b3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 75b524ddc470ef083fc1dc3032492361d080df2d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 12 Jun 2021 11:07:43 +1200 Subject: [PATCH 010/105] Fix formatting from cherry-pick conflict --- .../esp32_ble_server/ble_characteristic.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index 23b2693839..62775ab05f 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -148,39 +148,39 @@ bool BLECharacteristic::is_failed() { void BLECharacteristic::set_broadcast_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); } void BLECharacteristic::set_indicate_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); } void BLECharacteristic::set_notify_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); } void BLECharacteristic::set_read_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); } void BLECharacteristic::set_write_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); } void BLECharacteristic::set_write_no_response_property(bool value) { if (value) - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); else - this->properties_ = (esp_gatt_char_prop_t) (this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, From d8264166842a71af0c4d1a95894658b96b4bdb06 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:05:10 +1200 Subject: [PATCH 011/105] Allow no networks or AP to be set. (#1908) Co-authored-by: Paulus Schoutsen --- esphome/components/wifi/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index c45e179bc4..851c5f1b90 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -157,9 +157,7 @@ def _validate(config): config[CONF_NETWORKS] = cv.ensure_list(WIFI_NETWORK_STA)(network) if (CONF_NETWORKS not in config) and (CONF_AP not in config): - raise cv.Invalid( - "Please specify at least an SSID or an Access Point " "to create." - ) + config[CONF_NETWORKS] = [] if config.get(CONF_FAST_CONNECT, False): networks = config.get(CONF_NETWORKS, []) From 106f0d611f1bcfa14b9da79e753ecf7441dab88a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 13:16:43 +1200 Subject: [PATCH 012/105] Validate that either networks, ap, or improv is set up (#1910) --- esphome/components/wifi/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index 851c5f1b90..a701aa37e5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -137,6 +137,18 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) +def validate(config, item_config): + if ( + (CONF_NETWORKS in item_config) + and (item_config[CONF_NETWORKS] == []) + and (CONF_AP not in item_config) + ): + if "esp32_improv" not in config: + raise ValueError( + "Please specify at least an SSID or an Access Point to create." + ) + + def _validate(config): if CONF_PASSWORD in config and CONF_SSID not in config: raise cv.Invalid("Cannot have WiFi password without SSID!") From 744ca1af7c47f047f1414c31effe769079471e2b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 01:18:27 -0700 Subject: [PATCH 013/105] Bump frontend to 20210614.0 (#1912) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7f69865da..6ae20849da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210611.0 +esphome-dashboard==20210614.1 From 429caccefad7b747131c7511e7378c9ded829a12 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Tue, 15 Jun 2021 05:20:24 -0300 Subject: [PATCH 014/105] fixes compatibility with esphome cfg vscode (#1911) --- esphome/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 9af08a8f21..c7b2151728 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -504,6 +504,7 @@ def parse_args(argv): "version", "clean", "dashboard", + "vscode", ], ) @@ -686,7 +687,7 @@ def run_esphome(argv): CORE.dashboard = args.dashboard setup_log(args.verbose, args.quiet) - if args.deprecated_argv_suggestion is not None: + if args.deprecated_argv_suggestion is not None and args.command != "vscode": _LOGGER.warning( "Calling ESPHome with the configuration before the command is deprecated " "and will be removed in the future. " From e8bdbc45a9f4abd976dd4fadb3f916b7160c49c9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 15 Jun 2021 20:26:53 +1200 Subject: [PATCH 015/105] Bump version to v1.19.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f7ad39b95d..a17c847953 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b3" +PATCH_VERSION = "0b4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 28635124f9a6d57161ed328e9b1f57124eccbc01 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 15 Jun 2021 14:04:17 -0700 Subject: [PATCH 016/105] Bump dashboard to 20210615.0 (#1918) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6ae20849da..33436fb1b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210614.1 +esphome-dashboard==20210615.0 From 69e6cf2c0c887ed76abb56eb9a1925ffa9b2517b Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 09:06:50 +1200 Subject: [PATCH 017/105] Bump version to v1.19.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a17c847953..b9a22d2613 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b4" +PATCH_VERSION = "0b5" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From a5383fd2088d2622b30cfa23e45733b936e20059 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:49:06 +1200 Subject: [PATCH 018/105] Shorten the ble name to prevent crash with long device names (#1920) --- esphome/components/esp32_ble/ble.cpp | 11 ++++++++++- esphome/core/application.h | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index 18e80754df..d245cb9e4d 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -91,7 +91,16 @@ bool ESP32BLE::ble_setup_() { } } - err = esp_ble_gap_set_device_name(App.get_name().c_str()); + std::string name = App.get_name(); + if (name.length() > 20) { + if (App.is_name_add_mac_suffix_enabled()) { + name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address + } else { + name = name.substr(0, 20); + } + } + + err = esp_ble_gap_set_device_name(name.c_str()); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ble_gap_set_device_name failed: %d", err); return false; diff --git a/esphome/core/application.h b/esphome/core/application.h index aeda245161..97c99bd4f9 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -38,6 +38,7 @@ namespace esphome { class Application { public: void pre_setup(const std::string &name, const char *compilation_time, bool name_add_mac_suffix) { + this->name_add_mac_suffix_ = name_add_mac_suffix; if (name_add_mac_suffix) { this->name_ = name + "-" + get_mac_address().substr(6); } else { @@ -97,6 +98,8 @@ class Application { /// Get the name of this Application set by set_name(). const std::string &get_name() const { return this->name_; } + bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } + const std::string &get_compilation_time() const { return this->compilation_time_; } /** Set the target interval with which to run the loop() calls. @@ -245,6 +248,7 @@ class Application { std::string name_; std::string compilation_time_; + bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; int dump_config_at_{-1}; From fff3645901b760b10c6ed29f470a149b9963db87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 13:51:31 +1200 Subject: [PATCH 019/105] Bump version to v1.19.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index b9a22d2613..abaf55dece 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b5" +PATCH_VERSION = "0b6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 970563e07b997f2b6c7c5d5edc7a36a23be65af3 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 16 Jun 2021 21:00:51 +1200 Subject: [PATCH 020/105] Bump version to v1.19.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index abaf55dece..3492858240 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From f763daa577cad745c98e51803e5dcf69d16a4ac4 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Wed, 16 Jun 2021 19:39:04 +0200 Subject: [PATCH 021/105] Fix update-all from dashboard (#1924) --- esphome/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index c7b2151728..48f8bea083 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -394,7 +394,7 @@ def command_update_all(args): import click success = {} - files = list_yaml_files(args.configuration) + files = list_yaml_files(args.configuration[0]) twidth = 60 def print_bar(middle_text): @@ -408,7 +408,7 @@ def command_update_all(args): print("-" * twidth) print() rc = run_external_process( - "esphome", "--dashboard", "run", f, "--no-logs", "--device", "OTA" + "esphome", "--dashboard", "run", "--no-logs", "--device", "OTA", f ) if rc == 0: print_bar("[{}] {}".format(color(Fore.BOLD_GREEN, "SUCCESS"), f)) From f310ca1b748ca52b845ba04f6cd32bc794396c21 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 17 Jun 2021 05:40:55 +1200 Subject: [PATCH 022/105] Bump version to v1.19.0b7 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index abaf55dece..53acf3d84e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0b6" +PATCH_VERSION = "0b7" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 066c1022d0fb7915569c957dbeb1c8c7e3517968 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Jun 2021 12:35:54 -0700 Subject: [PATCH 023/105] Update dashboard to 20210617.0 (#1930) --- esphome/dashboard/dashboard.py | 28 +++++++++++++--------------- requirements.txt | 2 +- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 90061a3d4e..00b12199c0 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -716,9 +716,6 @@ class LogoutHandler(BaseHandler): self.redirect("./login") -_STATIC_FILE_HASHES = {} - - def get_base_frontend_path(): if ENV_DEV not in os.environ: import esphome_dashboard @@ -741,19 +738,23 @@ def get_static_path(*args): return os.path.join(get_base_frontend_path(), "static", *args) +@functools.lru_cache(maxsize=None) def get_static_file_url(name): + base = f"./static/{name}" + + if ENV_DEV in os.environ: + return base + # Module imports can't deduplicate if stuff added to url if name == "js/esphome/index.js": - return f"./static/{name}" + import esphome_dashboard - if name in _STATIC_FILE_HASHES: - hash_ = _STATIC_FILE_HASHES[name] - else: - path = get_static_path(name) - with open(path, "rb") as f_handle: - hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] - _STATIC_FILE_HASHES[name] = hash_ - return f"./static/{name}?hash={hash_}" + return base.replace("index.js", esphome_dashboard.entrypoint()) + + path = get_static_path(name) + with open(path, "rb") as f_handle: + hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8] + return f"{base}?hash={hash_}" def make_app(debug=get_bool_env(ENV_DEV)): @@ -820,9 +821,6 @@ def make_app(debug=get_bool_env(ENV_DEV)): **app_settings, ) - if debug: - _STATIC_FILE_HASHES.clear() - return app diff --git a/requirements.txt b/requirements.txt index 33436fb1b1..f02d65e518 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210615.0 +esphome-dashboard==20210617.1 From a045d001bf6101a63f4ef815449ec94c5b0fdf1e Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 17 Jun 2021 23:39:13 +0400 Subject: [PATCH 024/105] Fix: midea_ac: fixed query status frame (#1922) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index e90155bad3..a29345035b 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x00, - 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x68}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, + 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From f1dcf0f0b8266b5e8f12a069bd254479d189e36f Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 17 Jun 2021 21:54:14 +0200 Subject: [PATCH 025/105] Improve config final validation (#1917) --- esphome/components/cse7766/sensor.py | 9 +- esphome/components/dfplayer/__init__.py | 9 +- esphome/components/gps/__init__.py | 5 +- esphome/components/http_request/__init__.py | 68 +++++++-------- esphome/components/rc522_spi/__init__.py | 9 +- esphome/components/rtttl/__init__.py | 24 +++-- esphome/components/sim800l/__init__.py | 7 +- esphome/components/spi/__init__.py | 28 ++++-- esphome/components/uart/__init__.py | 97 +++++++++++++-------- esphome/components/wifi/__init__.py | 22 ++--- esphome/config.py | 76 ++++++++++------ esphome/config_validation.py | 8 +- esphome/core/__init__.py | 10 +-- esphome/cpp_helpers.py | 3 +- esphome/final_validate.py | 57 ++++++++++++ esphome/loader.py | 10 ++- esphome/storage_json.py | 5 +- esphome/types.py | 18 ++++ 18 files changed, 303 insertions(+), 162 deletions(-) create mode 100644 esphome/final_validate.py create mode 100644 esphome/types.py diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py index 98cf4da96d..4ccb346efd 100644 --- a/esphome/components/cse7766/sensor.py +++ b/esphome/components/cse7766/sensor.py @@ -45,6 +45,9 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("60s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "cse7766", baud_rate=4800, require_rx=True +) async def to_code(config): @@ -64,9 +67,3 @@ async def to_code(config): conf = config[CONF_POWER] sens = await sensor.new_sensor(conf) cg.add(var.set_power_sensor(sens)) - - -def validate(config, item_config): - uart.validate_device( - "cse7766", config, item_config, baud_rate=4800, require_tx=False - ) diff --git a/esphome/components/dfplayer/__init__.py b/esphome/components/dfplayer/__init__.py index 6af83888ab..3cdfc8ab85 100644 --- a/esphome/components/dfplayer/__init__.py +++ b/esphome/components/dfplayer/__init__.py @@ -68,6 +68,9 @@ CONFIG_SCHEMA = cv.All( } ).extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "dfplayer", baud_rate=9600, require_tx=True +) async def to_code(config): @@ -80,12 +83,6 @@ async def to_code(config): await automation.build_automation(trigger, [], conf) -def validate(config, item_config): - uart.validate_device( - "dfplayer", config, item_config, baud_rate=9600, require_rx=False - ) - - @automation.register_action( "dfplayer.play_next", NextAction, diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index de3eae1115..2867aa7325 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -62,6 +62,7 @@ CONFIG_SCHEMA = ( .extend(cv.polling_component_schema("20s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema("gps", require_rx=True) async def to_code(config): @@ -95,7 +96,3 @@ async def to_code(config): # https://platformio.org/lib/show/1655/TinyGPSPlus cg.add_library("1655", "1.0.2") # TinyGPSPlus, has name conflict - - -def validate(config, item_config): - uart.validate_device("gps", config, item_config, require_tx=False) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index 650602150a..f4475060df 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -2,6 +2,7 @@ import urllib.parse as urlparse import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.const import ( CONF_ID, @@ -14,7 +15,6 @@ from esphome.const import ( CONF_URL, ) from esphome.core import CORE, Lambda -from esphome.core.config import PLATFORMIO_ESP8266_LUT DEPENDENCIES = ["network"] AUTO_LOAD = ["json"] @@ -36,29 +36,6 @@ CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" -def validate_framework(config): - if CORE.is_esp32: - return config - - version = "RECOMMENDED" - if CONF_ARDUINO_VERSION in CORE.raw_config[CONF_ESPHOME]: - version = CORE.raw_config[CONF_ESPHOME][CONF_ARDUINO_VERSION] - - if version in ["LATEST", "DEV"]: - return config - - framework = ( - PLATFORMIO_ESP8266_LUT[version] - if version in PLATFORMIO_ESP8266_LUT - else version - ) - if framework < ARDUINO_VERSION_ESP8266["2.5.1"]: - raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1" - ) - return config - - def validate_url(value): value = cv.string(value) try: @@ -92,19 +69,36 @@ def validate_secure_url(config): return config -CONFIG_SCHEMA = ( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(HttpRequestComponent), - cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, - cv.Optional( - CONF_TIMEOUT, default="5s" - ): cv.positive_time_period_milliseconds, - } - ) - .add_extra(validate_framework) - .extend(cv.COMPONENT_SCHEMA) -) +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(HttpRequestComponent), + cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional(CONF_TIMEOUT, default="5s"): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +def validate_framework(config): + if CORE.is_esp32: + return + + # only for ESP8266 + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + version: str = fv.full_config.get().get_config_for_path(path) + + reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} + framework_version = reverse_map.get(version) + if framework_version is None or framework_version == "dev": + return + + if framework_version < "2.5.1": + raise cv.Invalid( + "This component is not supported on arduino framework version below 2.5.1", + path=[cv.ROOT_CONFIG_PATH] + path, + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(validate_framework) async def to_code(config): diff --git a/esphome/components/rc522_spi/__init__.py b/esphome/components/rc522_spi/__init__.py index 68b1e64145..77b0a99662 100644 --- a/esphome/components/rc522_spi/__init__.py +++ b/esphome/components/rc522_spi/__init__.py @@ -19,13 +19,12 @@ CONFIG_SCHEMA = cv.All( ).extend(spi.spi_device_schema(cs_pin_required=True)) ) +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "rc522_spi", require_miso=True, require_mosi=True +) + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await rc522.setup_rc522(var, config) await spi.register_spi_device(var, config) - - -def validate(config, item_config): - # validate given SPI hub is suitable for rc522_spi, it needs both miso and mosi - spi.validate_device("rc522_spi", config, item_config, True, True) diff --git a/esphome/components/rtttl/__init__.py b/esphome/components/rtttl/__init__.py index 7f860fe3d7..e9453896ac 100644 --- a/esphome/components/rtttl/__init__.py +++ b/esphome/components/rtttl/__init__.py @@ -1,6 +1,7 @@ import logging import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.components.output import FloatOutput from esphome.const import CONF_ID, CONF_OUTPUT, CONF_PLATFORM, CONF_TRIGGER_ID @@ -36,12 +37,8 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate(config, item_config): - # Not adding this to FloatOutput as this is the only component which needs `update_frequency` - - parent_config = config.get_config_by_id(item_config[CONF_OUTPUT]) - platform = parent_config[CONF_PLATFORM] - +def validate_parent_output_config(value): + platform = value.get(CONF_PLATFORM) PWM_GOOD = ["esp8266_pwm", "ledc"] PWM_BAD = [ "ac_dimmer ", @@ -55,14 +52,25 @@ def validate(config, item_config): ] if platform in PWM_BAD: - raise ValueError(f"Component rtttl cannot use {platform} as output component") + raise cv.Invalid(f"Component rtttl cannot use {platform} as output component") if platform not in PWM_GOOD: _LOGGER.warning( - "Component rtttl is not known to work with the selected output type. Make sure this output supports custom frequency output method." + "Component rtttl is not known to work with the selected output type. " + "Make sure this output supports custom frequency output method." ) +FINAL_VALIDATE_SCHEMA = cv.Schema( + { + cv.Required(CONF_OUTPUT): fv.id_declaration_match_schema( + validate_parent_output_config + ) + }, + extra=cv.ALLOW_EXTRA, +) + + async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 40c011a769..0887b8640f 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -40,6 +40,9 @@ CONFIG_SCHEMA = cv.All( .extend(cv.polling_component_schema("5s")) .extend(uart.UART_DEVICE_SCHEMA) ) +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "sim800l", baud_rate=9600, require_tx=True, require_rx=True +) async def to_code(config): @@ -54,10 +57,6 @@ async def to_code(config): ) -def validate(config, item_config): - uart.validate_device("sim800l", config, item_config, baud_rate=9600) - - SIM800L_SEND_SMS_SCHEMA = cv.Schema( { cv.GenerateID(): cv.use_id(Sim800LComponent), diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index e6e073c4a4..803a45814c 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins from esphome.const import ( CONF_CLK_PIN, @@ -69,9 +70,24 @@ async def register_spi_device(var, config): cg.add(var.set_cs_pin(pin)) -def validate_device(name, config, item_config, require_mosi, require_miso): - spi_config = config.get_config_by_id(item_config[CONF_SPI_ID]) - if require_mosi and CONF_MISO_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare miso_pin") - if require_miso and CONF_MOSI_PIN not in spi_config: - raise ValueError(f"Component {name} requires parent spi to declare mosi_pin") +def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): + hub_schema = {} + if require_miso: + hub_schema[ + cv.Required( + CONF_MISO_PIN, + msg=f"Component {name} requires this spi bus to declare a miso_pin", + ) + ] = cv.valid + if require_mosi: + hub_schema[ + cv.Required( + CONF_MOSI_PIN, + msg=f"Component {name} requires this spi bus to declare a mosi_pin", + ) + ] = cv.valid + + return cv.Schema( + {cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(hub_schema)}, + extra=cv.ALLOW_EXTRA, + ) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index aaed333e34..d2fcac2cb6 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -1,5 +1,8 @@ +from typing import Optional + import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import pins, automation from esphome.const import ( CONF_BAUD_RATE, @@ -92,42 +95,6 @@ async def to_code(config): cg.add(var.set_parity(config[CONF_PARITY])) -def validate_device( - name, config, item_config, baud_rate=None, require_tx=True, require_rx=True -): - if not hasattr(config, "uart_devices"): - config.uart_devices = {} - devices = config.uart_devices - - uart_config = config.get_config_by_id(item_config[CONF_UART_ID]) - - uart_id = uart_config[CONF_ID] - device = devices.setdefault(uart_id, {}) - - if require_tx: - if CONF_TX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare tx_pin") - if CONF_TX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_TX_PIN} as component {device[CONF_TX_PIN]} is already using it" - ) - device[CONF_TX_PIN] = name - - if require_rx: - if CONF_RX_PIN not in uart_config: - raise ValueError(f"Component {name} requires parent uart to declare rx_pin") - if CONF_RX_PIN in device: - raise ValueError( - f"Component {name} cannot use the same uart.{CONF_RX_PIN} as component {device[CONF_RX_PIN]} is already using it" - ) - device[CONF_RX_PIN] = name - - if baud_rate and uart_config[CONF_BAUD_RATE] != baud_rate: - raise ValueError( - f"Component {name} requires parent uart baud rate be {baud_rate}" - ) - - # A schema to use for all UART devices, all UART integrations must extend this! UART_DEVICE_SCHEMA = cv.Schema( { @@ -135,6 +102,64 @@ UART_DEVICE_SCHEMA = cv.Schema( } ) +KEY_UART_DEVICES = "uart_devices" + + +def final_validate_device_schema( + name: str, + *, + baud_rate: Optional[int] = None, + require_tx: bool = False, + require_rx: bool = False, +): + def validate_baud_rate(value): + if value != baud_rate: + raise cv.Invalid( + f"Component {name} required baud rate {baud_rate} for the uart bus" + ) + return value + + def validate_pin(opt, device): + def validator(value): + if opt in device: + raise cv.Invalid( + f"The uart {opt} is used both by {name} and {device[opt]}, " + f"but can only be used by one. Please create a new uart bus for {name}." + ) + device[opt] = name + return value + + return validator + + def validate_hub(hub_config): + hub_schema = {} + uart_id = hub_config[CONF_ID] + devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) + device = devices.setdefault(uart_id, {}) + + if require_tx: + hub_schema[ + cv.Required( + CONF_TX_PIN, + msg=f"Component {name} requires this uart bus to declare a tx_pin", + ) + ] = validate_pin(CONF_TX_PIN, device) + if require_rx: + hub_schema[ + cv.Required( + CONF_RX_PIN, + msg=f"Component {name} requires this uart bus to declare a rx_pin", + ) + ] = validate_pin(CONF_RX_PIN, device) + if baud_rate is not None: + hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate + return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config) + + return cv.Schema( + {cv.Required(CONF_UART_ID): fv.id_declaration_match_schema(validate_hub)}, + extra=cv.ALLOW_EXTRA, + ) + async def register_uart_device(var, config): """Register a UART device, setting up all the internal values. diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a701aa37e5..fa28eaffd4 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +import esphome.final_validate as fv from esphome import automation from esphome.automation import Condition from esphome.components.network import add_mdns_library @@ -137,16 +138,17 @@ WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend( ) -def validate(config, item_config): - if ( - (CONF_NETWORKS in item_config) - and (item_config[CONF_NETWORKS] == []) - and (CONF_AP not in item_config) - ): - if "esp32_improv" not in config: - raise ValueError( - "Please specify at least an SSID or an Access Point to create." - ) +def final_validate(config): + has_sta = bool(config.get(CONF_NETWORKS, True)) + has_ap = CONF_AP in config + has_improv = "esp32_improv" in fv.full_config.get() + if (not has_sta) and (not has_ap) and (not has_improv): + raise cv.Invalid( + "Please specify at least an SSID or an Access Point to create." + ) + + +FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) def _validate(config): diff --git a/esphome/config.py b/esphome/config.py index fcd2fac90f..93413a009c 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -21,11 +21,13 @@ from esphome.helpers import indent from esphome.util import safe_print, OrderedDict from typing import List, Optional, Tuple, Union -from esphome.core import ConfigType from esphome.loader import get_component, get_platform, ComponentManifest from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue from esphome.voluptuous_schema import ExtraKeysInvalid from esphome.log import color, Fore +import esphome.final_validate as fv +import esphome.config_validation as cv +from esphome.types import ConfigType, ConfigPathType, ConfigFragmentType _LOGGER = logging.getLogger(__name__) @@ -54,7 +56,7 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool return path[: len(other)] == other -class Config(OrderedDict): +class Config(OrderedDict, fv.FinalValidateConfig): def __init__(self): super().__init__() # A list of voluptuous errors @@ -65,6 +67,7 @@ class Config(OrderedDict): self.output_paths = [] # type: List[Tuple[ConfigPath, str]] # A list of components ids with the config path self.declare_ids = [] # type: List[Tuple[core.ID, ConfigPath]] + self._data = {} def add_error(self, error): # type: (vol.Invalid) -> None @@ -72,6 +75,12 @@ class Config(OrderedDict): for err in error.errors: self.add_error(err) return + if cv.ROOT_CONFIG_PATH in error.path: + # Root value means that the path before the root should be ignored + last_root = max( + i for i, v in enumerate(error.path) if v is cv.ROOT_CONFIG_PATH + ) + error.path = error.path[last_root + 1 :] self.errors.append(error) @contextmanager @@ -140,13 +149,16 @@ class Config(OrderedDict): return doc_range - def get_nested_item(self, path): - # type: (ConfigPath) -> ConfigType + def get_nested_item( + self, path: ConfigPathType, raise_error: bool = False + ) -> ConfigFragmentType: data = self for item_index in path: try: data = data[item_index] except (KeyError, IndexError, TypeError): + if raise_error: + raise return {} return data @@ -163,11 +175,20 @@ class Config(OrderedDict): part.append(item_index) return part - def get_config_by_id(self, id): + def get_path_for_id(self, id: core.ID): + """Return the config fragment where the given ID is declared.""" for declared_id, path in self.declare_ids: if declared_id.id == str(id): - return self.get_nested_item(path[:-1]) - return None + return path + raise KeyError(f"ID {id} not found in configuration") + + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + return self.get_nested_item(path, raise_error=True) + + @property + def data(self): + """Return temporary data used by final validation functions.""" + return self._data def iter_ids(config, path=None): @@ -189,23 +210,22 @@ def do_id_pass(result): # type: (Config) -> None from esphome.cpp_generator import MockObjClass from esphome.cpp_types import Component - declare_ids = result.declare_ids # type: List[Tuple[core.ID, ConfigPath]] searching_ids = [] # type: List[Tuple[core.ID, ConfigPath]] for id, path in iter_ids(result): if id.is_declaration: if id.id is not None: # Look for duplicate definitions - match = next((v for v in declare_ids if v[0].id == id.id), None) + match = next((v for v in result.declare_ids if v[0].id == id.id), None) if match is not None: opath = "->".join(str(v) for v in match[1]) result.add_str_error(f"ID {id.id} redefined! Check {opath}", path) continue - declare_ids.append((id, path)) + result.declare_ids.append((id, path)) else: searching_ids.append((id, path)) # Resolve default ids after manual IDs - for id, _ in declare_ids: - id.resolve([v[0].id for v in declare_ids]) + for id, _ in result.declare_ids: + id.resolve([v[0].id for v in result.declare_ids]) if isinstance(id.type, MockObjClass) and id.type.inherits_from(Component): CORE.component_ids.add(id.id) @@ -213,7 +233,7 @@ def do_id_pass(result): # type: (Config) -> None for id, path in searching_ids: if id.id is not None: # manually declared - match = next((v[0] for v in declare_ids if v[0].id == id.id), None) + match = next((v[0] for v in result.declare_ids if v[0].id == id.id), None) if match is None or not match.is_manual: # No declared ID with this name import difflib @@ -224,7 +244,7 @@ def do_id_pass(result): # type: (Config) -> None ) # Find candidates matches = difflib.get_close_matches( - id.id, [v[0].id for v in declare_ids if v[0].is_manual] + id.id, [v[0].id for v in result.declare_ids if v[0].is_manual] ) if matches: matches_s = ", ".join(f'"{x}"' for x in matches) @@ -245,7 +265,7 @@ def do_id_pass(result): # type: (Config) -> None if id.id is None and id.type is not None: matches = [] - for v in declare_ids: + for v in result.declare_ids: if v[0] is None or not isinstance(v[0].type, MockObjClass): continue inherits = v[0].type.inherits_from(id.type) @@ -278,8 +298,6 @@ def do_id_pass(result): # type: (Config) -> None def recursive_check_replaceme(value): - import esphome.config_validation as cv - if isinstance(value, list): return cv.Schema([recursive_check_replaceme])(value) if isinstance(value, dict): @@ -558,14 +576,16 @@ def validate_config(config, command_line_substitutions): # 7. Final validation if not result.errors: # Inter - components validation - for path, conf, comp in validate_queue: - if comp.config_schema is None: + token = fv.full_config.set(result) + + for path, _, comp in validate_queue: + if comp.final_validate_schema is None: continue - if callable(comp.validate): - try: - comp.validate(result, result.get_nested_item(path)) - except ValueError as err: - result.add_str_error(err, path) + conf = result.get_nested_item(path) + with result.catch_error(path): + comp.final_validate_schema(conf) + + fv.full_config.reset(token) return result @@ -621,8 +641,12 @@ def _format_vol_invalid(ex, config): ) elif "extra keys not allowed" in str(ex): message += "[{}] is an invalid option for [{}].".format(ex.path[-1], paren) - elif "required key not provided" in str(ex): - message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + elif isinstance(ex, vol.RequiredFieldInvalid): + if ex.msg == "required key not provided": + message += "'{}' is a required option for [{}].".format(ex.path[-1], paren) + else: + # Required has set a custom error message + message += ex.msg else: message += humanize_error(config, ex) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 2cdb6b0b76..7292cc3af5 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -75,6 +75,9 @@ Inclusive = vol.Inclusive ALLOW_EXTRA = vol.ALLOW_EXTRA UNDEFINED = vol.UNDEFINED RequiredFieldInvalid = vol.RequiredFieldInvalid +# this sentinel object can be placed in an 'Invalid' path to say +# the rest of the error path is relative to the root config path +ROOT_CONFIG_PATH = object() RESERVED_IDS = [ # C++ keywords http://en.cppreference.com/w/cpp/keyword @@ -218,8 +221,8 @@ class Required(vol.Required): - *not* the `config.get(CONF_)` syntax. """ - def __init__(self, key): - super().__init__(key) + def __init__(self, key, msg=None): + super().__init__(key, msg=msg) def check_not_templatable(value): @@ -1073,6 +1076,7 @@ def invalid(message): def valid(value): + """A validator that is always valid and returns the value as-is.""" return value diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 1841dfd8be..df98e1b150 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -2,7 +2,7 @@ import logging import math import os import re -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from esphome.const import ( CONF_ARDUINO_VERSION, @@ -23,6 +23,7 @@ from esphome.util import OrderedDict if TYPE_CHECKING: from ..cpp_generator import MockObj, MockObjClass, Statement + from ..types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -462,9 +463,9 @@ class EsphomeCore: # The board that's used (for example nodemcuv2) self.board: Optional[str] = None # The full raw configuration - self.raw_config: Optional[ConfigType] = None + self.raw_config: Optional["ConfigType"] = None # The validated configuration, this is None until the config has been validated - self.config: Optional[ConfigType] = None + self.config: Optional["ConfigType"] = None # The pending tasks in the task queue (mostly for C++ generation) # This is a priority queue (with heapq) # Each item is a tuple of form: (-priority, unique number, task) @@ -752,6 +753,3 @@ class EnumValue: CORE = EsphomeCore() - -ConfigType = Dict[str, Any] -CoreType = EsphomeCore diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py index 1c52f38e50..1d66eabf6c 100644 --- a/esphome/cpp_helpers.py +++ b/esphome/cpp_helpers.py @@ -8,7 +8,8 @@ from esphome.const import ( ) # pylint: disable=unused-import -from esphome.core import coroutine, ID, CORE, ConfigType +from esphome.core import coroutine, ID, CORE +from esphome.types import ConfigType from esphome.cpp_generator import RawExpression, add, get_variable from esphome.cpp_types import App, GPIOPin from esphome.util import Registry, RegistryEntry diff --git a/esphome/final_validate.py b/esphome/final_validate.py new file mode 100644 index 0000000000..50fdbaf3f4 --- /dev/null +++ b/esphome/final_validate.py @@ -0,0 +1,57 @@ +from abc import ABC, abstractmethod, abstractproperty +from typing import Dict, Any +import contextvars + +from esphome.types import ConfigFragmentType, ID, ConfigPathType +import esphome.config_validation as cv + + +class FinalValidateConfig(ABC): + @abstractproperty + def data(self) -> Dict[str, Any]: + """A dictionary that can be used by post validation functions to store + global data during the validation phase. Each component should store its + data under a unique key + """ + + @abstractmethod + def get_path_for_id(self, id: ID) -> ConfigPathType: + """Get the config path a given ID has been declared in. + + This is the location under the _validated_ config (for example, with cv.ensure_list applied) + Raises KeyError if the id was not declared in the configuration. + """ + + @abstractmethod + def get_config_for_path(self, path: ConfigPathType) -> ConfigFragmentType: + """Get the config fragment for the given global path. + + Raises KeyError if a key in the path does not exist. + """ + + +FinalValidateConfig.register(dict) + +# Context variable tracking the full config for some final validation functions. +full_config: contextvars.ContextVar[FinalValidateConfig] = contextvars.ContextVar( + "full_config" +) + + +def id_declaration_match_schema(schema): + """A final-validation schema function that applies a schema to the outer config fragment of an + ID declaration. + + This validator must be applied to ID values. + """ + if not isinstance(schema, cv.Schema): + schema = cv.Schema(schema, extra=cv.ALLOW_EXTRA) + + def validator(value): + fconf = full_config.get() + path = fconf.get_path_for_id(value)[:-1] + declaration_config = fconf.get_config_for_path(path) + with cv.prepend_path([cv.ROOT_CONFIG_PATH] + path): + return schema(declaration_config) + + return validator diff --git a/esphome/loader.py b/esphome/loader.py index d9d407d787..f74fc6367d 100644 --- a/esphome/loader.py +++ b/esphome/loader.py @@ -12,6 +12,7 @@ from pathlib import Path from esphome.const import ESP_PLATFORMS, SOURCE_FILE_EXTENSIONS import esphome.core.config from esphome.core import CORE +from esphome.types import ConfigType _LOGGER = logging.getLogger(__name__) @@ -81,8 +82,13 @@ class ComponentManifest: return getattr(self.module, "CODEOWNERS", []) @property - def validate(self): - return getattr(self.module, "validate", None) + def final_validate_schema(self) -> Optional[Callable[[ConfigType], None]]: + """Components can declare a `FINAL_VALIDATE_SCHEMA` cv.Schema that gets called + after the main validation. In that function checks across components can be made. + + Note that the function can't mutate the configuration - no changes are saved + """ + return getattr(self.module, "FINAL_VALIDATE_SCHEMA", None) @property def source_files(self) -> Dict[Path, SourceFile]: diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 6f81e0d96a..c0fbc6edf7 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -4,14 +4,13 @@ from datetime import datetime import json import logging import os +from typing import Any, Optional, List from esphome import const from esphome.core import CORE from esphome.helpers import write_file_if_changed -# pylint: disable=unused-import, wrong-import-order -from esphome.core import CoreType -from typing import Any, Optional, List +from esphome.types import CoreType _LOGGER = logging.getLogger(__name__) diff --git a/esphome/types.py b/esphome/types.py new file mode 100644 index 0000000000..6bbfb00ce6 --- /dev/null +++ b/esphome/types.py @@ -0,0 +1,18 @@ +"""This helper module tracks commonly used types in the esphome python codebase.""" +from typing import Dict, Union, List + +from esphome.core import ID, Lambda, EsphomeCore + +ConfigFragmentType = Union[ + str, + int, + float, + None, + Dict[Union[str, int], "ConfigFragmentType"], + List["ConfigFragmentType"], + ID, + Lambda, +] +ConfigType = Dict[str, ConfigFragmentType] +CoreType = EsphomeCore +ConfigPathType = Union[str, int] From dd875e7529ef57abbed6c1ad549226eef28864a5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:48:40 +1200 Subject: [PATCH 026/105] Replace CLIMATE_MODE_AUTO with CLIMATE_MODE_HEAT_COOL in most cases (#1933) --- esphome/components/bang_bang/bang_bang_climate.cpp | 6 +++--- esphome/components/climate/climate.h | 2 +- esphome/components/climate/climate_traits.cpp | 5 +++++ esphome/components/climate/climate_traits.h | 2 ++ esphome/components/climate_ir/climate_ir.cpp | 2 +- esphome/components/climate_ir_lg/climate_ir_lg.cpp | 12 ++++++------ esphome/components/coolix/coolix.cpp | 8 ++++---- esphome/components/daikin/daikin.cpp | 6 +++--- .../components/fujitsu_general/fujitsu_general.cpp | 4 ++-- esphome/components/hitachi_ac344/hitachi_ac344.cpp | 4 ++-- esphome/components/midea_ac/midea_climate.cpp | 2 +- esphome/components/midea_ac/midea_frame.cpp | 4 ++-- esphome/components/mitsubishi/mitsubishi.cpp | 2 +- esphome/components/pid/pid_climate.cpp | 10 +++++----- esphome/components/tcl112/tcl112.cpp | 4 ++-- esphome/components/thermostat/climate.py | 8 ++++---- .../components/thermostat/thermostat_climate.cpp | 13 +++++++++---- esphome/components/thermostat/thermostat_climate.h | 2 ++ esphome/components/toshiba/toshiba.cpp | 4 ++-- esphome/components/tuya/climate/tuya_climate.cpp | 2 +- esphome/components/whirlpool/whirlpool.cpp | 4 ++-- esphome/components/yashima/yashima.cpp | 4 ++-- 22 files changed, 62 insertions(+), 48 deletions(-) diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 45d5174390..ea1442755d 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,7 +21,7 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } } @@ -41,7 +41,7 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(true); @@ -50,7 +50,7 @@ climate::ClimateTraits BangBangClimate::traits() { return traits; } void BangBangClimate::compute_state_() { - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { // in non-auto mode, switch directly to appropriate action // - HEAT mode -> HEATING action // - COOL mode -> COOLING action diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index cd69469692..3aa29be2fb 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -159,7 +159,7 @@ struct ClimateDeviceRestoreState { * * The entire state of the climate device is encoded in public properties of the base class (current_temperature, * mode etc). These are read-only for the user and rw for integrations. The reason these are public - * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_AUTO) ...` + * is for simple access to them from lambdas `if (id(my_climate).mode == climate::CLIMATE_MODE_HEAT_COOL) ...` */ class Climate : public Nameable { public: diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index eda4722fcb..774ada785f 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -8,6 +8,8 @@ bool ClimateTraits::supports_mode(ClimateMode mode) const { switch (mode) { case CLIMATE_MODE_OFF: return true; + case CLIMATE_MODE_HEAT_COOL: + return this->supports_heat_cool_mode_; case CLIMATE_MODE_AUTO: return this->supports_auto_mode_; case CLIMATE_MODE_COOL: @@ -31,6 +33,9 @@ void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_ supports_two_point_target_temperature_ = supports_two_point_target_temperature; } void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } +void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { + supports_heat_cool_mode_ = supports_heat_cool_mode; +} void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f0a48ca308..f8e6f87306 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -46,6 +46,7 @@ class ClimateTraits { bool get_supports_two_point_target_temperature() const; void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); void set_supports_auto_mode(bool supports_auto_mode); + void set_supports_heat_cool_mode(bool supports_heat_cool_mode); void set_supports_cool_mode(bool supports_cool_mode); void set_supports_heat_mode(bool supports_heat_mode); void set_supports_fan_only_mode(bool supports_fan_only_mode); @@ -100,6 +101,7 @@ class ClimateTraits { bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; bool supports_auto_mode_{false}; + bool supports_heat_cool_mode_{false}; bool supports_cool_mode_{false}; bool supports_heat_mode_{false}; bool supports_fan_only_mode_{false}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 8f06ff2214..f88b2174ee 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,7 +9,7 @@ static const char *TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_dry_mode(this->supports_dry_); diff --git a/esphome/components/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index 983d33c0b1..d675883fcf 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -39,7 +39,7 @@ void LgIrClimate::transmit_state() { send_swing_cmd_ = false; remote_state |= COMMAND_SWING; } else { - if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_AUTO) { + if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { remote_state |= COMMAND_ON_AI; } else if (mode_before_ == climate::CLIMATE_MODE_OFF && this->mode != climate::CLIMATE_MODE_OFF) { remote_state |= COMMAND_ON; @@ -52,7 +52,7 @@ void LgIrClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COMMAND_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COMMAND_AUTO; break; case climate::CLIMATE_MODE_DRY: @@ -89,7 +89,7 @@ void LgIrClimate::transmit_state() { } } - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; // remote_state |= FAN_MODE_AUTO_DRY; } @@ -128,7 +128,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COMMAND_MASK) == COMMAND_ON) { this->mode = climate::CLIMATE_MODE_COOL; } else if ((remote_state & COMMAND_MASK) == COMMAND_ON_AI) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } if ((remote_state & COMMAND_MASK) == COMMAND_OFF) { @@ -138,7 +138,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) this->mode = climate::CLIMATE_MODE_DRY; else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { @@ -152,7 +152,7 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->target_temperature = ((remote_state & TEMP_MASK) >> TEMP_SHIFT) + 15; // Fan Speed - if (this->mode == climate::CLIMATE_MODE_AUTO) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL) { this->fan_mode = climate::CLIMATE_FAN_AUTO; } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_DRY) { diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index e50521a348..012744b3e9 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -70,7 +70,7 @@ void CoolixClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state |= COOLIX_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state |= COOLIX_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -89,7 +89,7 @@ void CoolixClimate::transmit_state() { } else { remote_state |= COOLIX_FAN_TEMP_CODE; } - if (this->mode == climate::CLIMATE_MODE_AUTO || this->mode == climate::CLIMATE_MODE_DRY) { + if (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) { this->fan_mode = climate::CLIMATE_FAN_AUTO; remote_state |= COOLIX_FAN_MODE_AUTO_DRY; } else { @@ -197,7 +197,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) this->mode = climate::CLIMATE_MODE_HEAT; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) this->mode = climate::CLIMATE_MODE_DRY; @@ -207,7 +207,7 @@ bool CoolixClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_COOL; // Fan Speed - if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_AUTO || + if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_DRY) this->fan_mode = climate::CLIMATE_FAN_AUTO; else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index e0ffd46387..dca3bffbac 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -77,7 +77,7 @@ uint8_t DaikinClimate::operation_mode_() { case climate::CLIMATE_MODE_HEAT: operating_mode |= DAIKIN_MODE_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: operating_mode |= DAIKIN_MODE_AUTO; break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -131,7 +131,7 @@ uint8_t DaikinClimate::temperature_() { switch (this->mode) { case climate::CLIMATE_MODE_FAN_ONLY: return 0x32; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_DRY: return 0xc0; default: @@ -160,7 +160,7 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case DAIKIN_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case DAIKIN_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 8671f38e8e..2e93a98e52 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -132,7 +132,7 @@ void FujitsuGeneralClimate::transmit_state() { case climate::CLIMATE_MODE_FAN_ONLY: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_FAN); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: SET_NIBBLE(remote_state, FUJITSU_GENERAL_MODE_NIBBLE, FUJITSU_GENERAL_MODE_AUTO); break; @@ -343,7 +343,7 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { case FUJITSU_GENERAL_MODE_AUTO: default: // TODO: CLIMATE_MODE_10C is missing from esphome - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index b2798b608a..86f82d8bbb 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -155,7 +155,7 @@ void HitachiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: set_mode_(HITACHI_AC344_MODE_HEAT); break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: set_mode_(HITACHI_AC344_MODE_AUTO); break; case climate::CLIMATE_MODE_FAN_ONLY: @@ -251,7 +251,7 @@ bool HitachiClimate::parse_mode_(const uint8_t remote_state[]) { this->mode = climate::CLIMATE_MODE_HEAT; break; case HITACHI_AC344_MODE_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; case HITACHI_AC344_MODE_FAN: this->mode = climate::CLIMATE_MODE_FAN_ONLY; diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index f98cf74ac1..481a6da54d 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,7 +167,7 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(true); traits.set_supports_dry_mode(true); traits.set_supports_heat_mode(true); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index a29345035b..0a09e86de7 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -44,7 +44,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { return climate::CLIMATE_MODE_OFF; switch (this->pbuf_[12] >> 5) { case MIDEA_MODE_AUTO: - return climate::CLIMATE_MODE_AUTO; + return climate::CLIMATE_MODE_HEAT_COOL; case MIDEA_MODE_COOL: return climate::CLIMATE_MODE_COOL; case MIDEA_MODE_DRY: @@ -61,7 +61,7 @@ climate::ClimateMode PropertiesFrame::get_mode() const { void PropertiesFrame::set_mode(climate::ClimateMode mode) { uint8_t m; switch (mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: m = MIDEA_MODE_AUTO; break; case climate::CLIMATE_MODE_COOL: diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index dbc70af75c..c9d0ce842e 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -33,7 +33,7 @@ void MitsubishiClimate::transmit_state() { case climate::CLIMATE_MODE_HEAT: remote_state[6] = MITSUBISHI_HEAT; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] = MITSUBISHI_AUTO; break; case climate::CLIMATE_MODE_OFF: diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 24fb0ec905..0423ab27fc 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,7 +20,7 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->target_temperature = this->default_target_temperature_; } } @@ -31,7 +31,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { this->target_temperature = *call.get_target_temperature(); // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_AUTO) + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) this->handle_non_auto_mode_(); this->publish_state(); @@ -39,7 +39,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); traits.set_supports_cool_mode(this->supports_cool_()); traits.set_supports_heat_mode(this->supports_heat_()); @@ -121,14 +121,14 @@ void PIDClimate::update_pid_() { // keep autotuner instance so that subsequent dump_configs will print the long result message. } else { value = res.output; - if (mode != climate::CLIMATE_MODE_AUTO) { + if (mode != climate::CLIMATE_MODE_HEAT_COOL) { ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!"); } } } } - if (this->mode != climate::CLIMATE_MODE_AUTO) { + if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) { this->handle_non_auto_mode_(); } else { this->write_output_(value); diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index 91cec27094..6921bbd3c0 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -47,7 +47,7 @@ void Tcl112Climate::transmit_state() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[6] &= 0xF0; remote_state[6] |= TCL112_AUTO; break; @@ -204,7 +204,7 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case TCL112_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/thermostat/climate.py b/esphome/components/thermostat/climate.py index 4a371ec165..07a94fd184 100644 --- a/esphome/components/thermostat/climate.py +++ b/esphome/components/thermostat/climate.py @@ -227,7 +227,7 @@ async def to_code(config): await cg.register_component(var, config) await climate.register_climate(var, config) - auto_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config + heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config two_points_available = CONF_HEAT_ACTION in config and ( CONF_COOL_ACTION in config or CONF_FAN_ONLY_ACTION in config ) @@ -258,10 +258,10 @@ async def to_code(config): var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION] ) - if auto_mode_available is True: - cg.add(var.set_supports_auto(True)) + if heat_cool_mode_available is True: + cg.add(var.set_supports_heat_cool(True)) else: - cg.add(var.set_supports_auto(False)) + cg.add(var.set_supports_heat_cool(False)) if CONF_COOL_ACTION in config: await automation.build_automation( diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 3bab0e85fd..a96c702473 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -21,7 +21,7 @@ void ThermostatClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles temps for us - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; this->change_away_(false); } // refresh the climate action based on the restored settings @@ -79,6 +79,7 @@ climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); traits.set_supports_auto_mode(this->supports_auto_); + traits.set_supports_heat_cool_mode(this->supports_heat_cool_); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_dry_mode(this->supports_dry_); traits.set_supports_fan_only_mode(this->supports_fan_only_); @@ -130,7 +131,7 @@ climate::ClimateAction ThermostatClimate::compute_action_() { case climate::CLIMATE_MODE_OFF: target_action = climate::CLIMATE_ACTION_OFF; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_HEAT: if (this->supports_cool_) { @@ -321,7 +322,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { case climate::CLIMATE_MODE_OFF: trig = this->off_mode_trigger_; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // trig = this->auto_mode_trigger_; break; case climate::CLIMATE_MODE_COOL: @@ -339,7 +340,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode) { default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value - mode = climate::CLIMATE_MODE_AUTO; + mode = climate::CLIMATE_MODE_HEAT_COOL; // trig = this->auto_mode_trigger_; } assert(trig != nullptr); @@ -434,6 +435,9 @@ ThermostatClimate::ThermostatClimate() swing_mode_vertical_trigger_(new Trigger<>()) {} void ThermostatClimate::set_hysteresis(float hysteresis) { this->hysteresis_ = hysteresis; } void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } +void ThermostatClimate::set_supports_heat_cool(bool supports_heat_cool) { + this->supports_heat_cool_ = supports_heat_cool; +} void ThermostatClimate::set_supports_auto(bool supports_auto) { this->supports_auto_ = supports_auto; } void ThermostatClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void ThermostatClimate::set_supports_dry(bool supports_dry) { this->supports_dry_ = supports_dry; } @@ -521,6 +525,7 @@ void ThermostatClimate::dump_config() { } ESP_LOGCONFIG(TAG, " Hysteresis: %.1f°C", this->hysteresis_); ESP_LOGCONFIG(TAG, " Supports AUTO: %s", YESNO(this->supports_auto_)); + ESP_LOGCONFIG(TAG, " Supports HEAT/COOL: %s", YESNO(this->supports_heat_cool_)); ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); ESP_LOGCONFIG(TAG, " Supports DRY: %s", YESNO(this->supports_dry_)); ESP_LOGCONFIG(TAG, " Supports FAN_ONLY: %s", YESNO(this->supports_fan_only_)); diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 86a1007efa..3fd482da53 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -29,6 +29,7 @@ class ThermostatClimate : public climate::Climate, public Component { void set_hysteresis(float hysteresis); void set_sensor(sensor::Sensor *sensor); void set_supports_auto(bool supports_auto); + void set_supports_heat_cool(bool supports_heat_cool); void set_supports_cool(bool supports_cool); void set_supports_dry(bool supports_dry); void set_supports_fan_only(bool supports_fan_only); @@ -113,6 +114,7 @@ class ThermostatClimate : public climate::Climate, public Component { /// A false value for any given attribute means that the controller has no such action /// (for example a thermostat, where only heating and not-heating is possible). bool supports_auto_{false}; + bool supports_heat_cool_{false}; bool supports_cool_{false}; bool supports_dry_{false}; bool supports_fan_only_{false}; diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 33e2831dd3..b932516edf 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -80,7 +80,7 @@ void ToshibaClimate::transmit_state() { mode = TOSHIBA_MODE_COOL; break; - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: default: mode = TOSHIBA_MODE_AUTO; } @@ -190,7 +190,7 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { case TOSHIBA_MODE_AUTO: default: /* Note: Dry and Fan-only modes are reported as Auto, as they are not supported yet */ - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } /* Get the target temperature */ diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 962d18a391..9c9cf9f3e7 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -13,7 +13,7 @@ void TuyaClimate::setup() { this->mode = climate::CLIMATE_MODE_OFF; if (datapoint.value_bool) { if (this->supports_heat_ && this->supports_cool_) { - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; } else if (this->supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT; } else if (this->supports_cool_) { diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index e7c93246f2..5a1e025a38 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -48,7 +48,7 @@ void WhirlpoolClimate::transmit_state() { this->powered_on_assumed = powered_on; } switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: // set fan auto // set temp auto temp // set sleep false @@ -239,7 +239,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; break; case WHIRLPOOL_AUTO: - this->mode = climate::CLIMATE_MODE_AUTO; + this->mode = climate::CLIMATE_MODE_HEAT_COOL; break; } } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index e3c0a33127..e53e5fcccc 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,7 +82,7 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_auto_mode(true); + traits.set_supports_heat_cool_mode(true); traits.set_supports_cool_mode(this->supports_cool_); traits.set_supports_heat_mode(this->supports_heat_); traits.set_supports_two_point_target_temperature(false); @@ -139,7 +139,7 @@ void YashimaClimate::transmit_state_() { // Set mode switch (this->mode) { - case climate::CLIMATE_MODE_AUTO: + case climate::CLIMATE_MODE_HEAT_COOL: remote_state[0] |= YASHIMA_MODE_AUTO_BYTE0; remote_state[5] |= YASHIMA_MODE_AUTO_BYTE5; break; From 01904a0f107c92d55a51fba93f6c31a91b5259eb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 18 Jun 2021 11:52:02 +1200 Subject: [PATCH 027/105] Bump version to v1.19.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 3492858240..733931ce09 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "0" +PATCH_VERSION = "1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 4fa959ba45bb6b81729ac0644d780b559bea5016 Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Thu, 17 Jun 2021 21:58:39 -0500 Subject: [PATCH 028/105] Don't send Tuya commands while currently receiving a message (#1886) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 3f09db068d..d1f8b8c9e2 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -333,7 +333,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !command_queue_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } From 8aec092ab62a9c6d4ba436a51071144006e29b39 Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Mon, 21 Jun 2021 00:59:12 +0400 Subject: [PATCH 029/105] Fix midea_ac query frame (#1940) --- esphome/components/midea_ac/midea_frame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 0a09e86de7..a624400411 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -8,9 +8,9 @@ const std::string MIDEA_SILENT_FAN_MODE = "silent"; const std::string MIDEA_TURBO_FAN_MODE = "turbo"; const std::string MIDEA_FREEZE_PROTECTION_PRESET = "freeze protection"; -const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x61, - 0x00, 0xFF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE3, 0xA8}; +const uint8_t QueryFrame::INIT[] = {0xAA, 0x21, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x41, 0x81, + 0x00, 0xFF, 0x03, 0xFF, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x37, 0x31}; const uint8_t PowerQueryFrame::INIT[] = {0xAA, 0x22, 0xAC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x41, 0x21, 0x01, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, From d73a44c50435a108bdb64586dc46c719dd2f6406 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 09:00:16 +1200 Subject: [PATCH 030/105] Allow wifi setup to proceed when there is no sta or ap (#1931) --- esphome/components/wifi/wifi_component.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index e98754fac7..7d6cb1347e 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -587,7 +587,7 @@ void WiFiComponent::retry_connect() { } bool WiFiComponent::can_proceed() { - if (this->has_ap() && !this->has_sta()) { + if (!this->has_sta()) { return true; } return this->is_connected(); From 969834e0375432b39df2dc32bbf0ce6ddeeb4a28 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:17:38 +1200 Subject: [PATCH 031/105] Fix bad climate control enum (#1942) --- esphome/components/climate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/climate/__init__.py b/esphome/components/climate/__init__.py index 5a4492216e..88991ff795 100644 --- a/esphome/components/climate/__init__.py +++ b/esphome/components/climate/__init__.py @@ -36,7 +36,7 @@ ClimateTraits = climate_ns.class_("ClimateTraits") ClimateMode = climate_ns.enum("ClimateMode") CLIMATE_MODES = { "OFF": ClimateMode.CLIMATE_MODE_OFF, - "HEAT_COOL": ClimateMode.CLIMATE_HEAT_COOL, + "HEAT_COOL": ClimateMode.CLIMATE_MODE_HEAT_COOL, "COOL": ClimateMode.CLIMATE_MODE_COOL, "HEAT": ClimateMode.CLIMATE_MODE_HEAT, "DRY": ClimateMode.CLIMATE_MODE_DRY, From 2a5def10e74f6f831e9d784411348db8378c8bc2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 21 Jun 2021 14:40:05 +1200 Subject: [PATCH 032/105] Bump version to v1.19.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 733931ce09..35959caaff 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "1" +PATCH_VERSION = "2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 89dfa5ea82d1cbb61cb80385e1c307153f50aea4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:15:11 +1200 Subject: [PATCH 033/105] Bump esphome-dashboard to 20210622.0 (#1955) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f02d65e518..12500d57b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210617.1 +esphome-dashboard==20210622.0 From 150114d774b36f6c7af56b187aa8b2a8624a5297 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 23 Jun 2021 19:39:37 +1200 Subject: [PATCH 034/105] Bump version to v1.19.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 35959caaff..de33ebc2de 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "2" +PATCH_VERSION = "3" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From d4eb0f16550db1cda3573cee2ff76eda9aaa4d36 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 21:17:01 +0200 Subject: [PATCH 035/105] Rework climate traits (#1941) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/api/api.proto | 16 +- esphome/components/api/api_connection.cpp | 52 ++--- esphome/components/api/api_pb2.cpp | 40 ++-- esphome/components/api/api_pb2.h | 12 +- .../bang_bang/bang_bang_climate.cpp | 23 +- esphome/components/climate/automation.h | 4 +- esphome/components/climate/climate.cpp | 74 +++--- esphome/components/climate/climate.h | 8 +- esphome/components/climate/climate_mode.cpp | 4 +- esphome/components/climate/climate_mode.h | 9 +- esphome/components/climate/climate_traits.cpp | 203 ---------------- esphome/components/climate/climate_traits.h | 219 +++++++++++------- esphome/components/climate_ir/climate_ir.cpp | 65 +----- esphome/components/climate_ir/climate_ir.h | 9 +- esphome/components/daikin/daikin.h | 11 +- .../components/hitachi_ac344/hitachi_ac344.h | 9 +- esphome/components/midea_ac/midea_climate.cpp | 48 ++-- esphome/components/midea_ac/midea_climate.h | 18 +- esphome/components/mqtt/mqtt_climate.cpp | 14 +- esphome/components/pid/pid_climate.cpp | 10 +- .../thermostat/thermostat_climate.cpp | 73 ++++-- .../components/tuya/climate/tuya_climate.cpp | 6 +- esphome/components/yashima/yashima.cpp | 11 +- script/api_protobuf/api_protobuf.py | 1 + 24 files changed, 393 insertions(+), 546 deletions(-) mode change 100644 => 100755 script/api_protobuf/api_protobuf.py diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index bdb94b3d9b..87a7cf4749 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,11 +710,11 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_ECO = 0; + CLIMATE_PRESET_HOME = 0; CLIMATE_PRESET_AWAY = 1; CLIMATE_PRESET_BOOST = 2; CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_HOME = 4; + CLIMATE_PRESET_ECO = 4; CLIMATE_PRESET_SLEEP = 5; CLIMATE_PRESET_ACTIVITY = 6; } @@ -734,7 +734,9 @@ message ListEntitiesClimateResponse { float visual_min_temperature = 8; float visual_max_temperature = 9; float visual_temperature_step = 10; - bool supports_away = 11; + // for older peer versions - in new system this + // is if CLIMATE_PRESET_AWAY exists is supported_presets + bool legacy_supports_away = 11; bool supports_action = 12; repeated ClimateFanMode supported_fan_modes = 13; repeated ClimateSwingMode supported_swing_modes = 14; @@ -754,7 +756,8 @@ message ClimateStateResponse { float target_temperature = 4; float target_temperature_low = 5; float target_temperature_high = 6; - bool away = 7; + // For older peers, equal to preset == CLIMATE_PRESET_AWAY + bool legacy_away = 7; ClimateAction action = 8; ClimateFanMode fan_mode = 9; ClimateSwingMode swing_mode = 10; @@ -777,8 +780,9 @@ message ClimateCommandRequest { float target_temperature_low = 7; bool has_target_temperature_high = 8; float target_temperature_high = 9; - bool has_away = 10; - bool away = 11; + // legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset + bool has_legacy_away = 10; + bool legacy_away = 11; bool has_fan_mode = 12; ClimateFanMode fan_mode = 13; bool has_swing_mode = 14; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 175c3c4487..68187f259e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -475,14 +475,14 @@ bool APIConnection::send_climate_state(climate::Climate *climate) { } else { resp.target_temperature = climate->target_temperature; } - if (traits.get_supports_away()) - resp.away = climate->away; if (traits.get_supports_fan_modes() && climate->fan_mode.has_value()) resp.fan_mode = static_cast(climate->fan_mode.value()); if (!traits.get_supported_custom_fan_modes().empty() && climate->custom_fan_mode.has_value()) resp.custom_fan_mode = climate->custom_fan_mode.value(); - if (traits.get_supports_presets() && climate->preset.has_value()) + if (traits.get_supports_presets() && climate->preset.has_value()) { resp.preset = static_cast(climate->preset.value()); + resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY; + } if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value()) resp.custom_preset = climate->custom_preset.value(); if (traits.get_supports_swing_modes()) @@ -498,40 +498,26 @@ bool APIConnection::send_climate_info(climate::Climate *climate) { msg.unique_id = get_default_unique_id("climate", climate); msg.supports_current_temperature = traits.get_supports_current_temperature(); msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature(); - for (auto mode : - {climate::CLIMATE_MODE_AUTO, climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, - climate::CLIMATE_MODE_DRY, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_HEAT_COOL}) { - if (traits.supports_mode(mode)) - msg.supported_modes.push_back(static_cast(mode)); - } + + for (auto mode : traits.get_supported_modes()) + msg.supported_modes.push_back(static_cast(mode)); + msg.visual_min_temperature = traits.get_visual_min_temperature(); msg.visual_max_temperature = traits.get_visual_max_temperature(); msg.visual_temperature_step = traits.get_visual_temperature_step(); - msg.supports_away = traits.get_supports_away(); + msg.legacy_supports_away = traits.supports_preset(climate::CLIMATE_PRESET_AWAY); msg.supports_action = traits.get_supports_action(); - for (auto fan_mode : {climate::CLIMATE_FAN_ON, climate::CLIMATE_FAN_OFF, climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH, - climate::CLIMATE_FAN_MIDDLE, climate::CLIMATE_FAN_FOCUS, climate::CLIMATE_FAN_DIFFUSE}) { - if (traits.supports_fan_mode(fan_mode)) - msg.supported_fan_modes.push_back(static_cast(fan_mode)); - } - for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) { + + for (auto fan_mode : traits.get_supported_fan_modes()) + msg.supported_fan_modes.push_back(static_cast(fan_mode)); + for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) msg.supported_custom_fan_modes.push_back(custom_fan_mode); - } - for (auto preset : {climate::CLIMATE_PRESET_ECO, climate::CLIMATE_PRESET_AWAY, climate::CLIMATE_PRESET_BOOST, - climate::CLIMATE_PRESET_COMFORT, climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_SLEEP, - climate::CLIMATE_PRESET_ACTIVITY}) { - if (traits.supports_preset(preset)) - msg.supported_presets.push_back(static_cast(preset)); - } - for (auto const &custom_preset : traits.get_supported_custom_presets()) { + for (auto preset : traits.get_supported_presets()) + msg.supported_presets.push_back(static_cast(preset)); + for (auto const &custom_preset : traits.get_supported_custom_presets()) msg.supported_custom_presets.push_back(custom_preset); - } - for (auto swing_mode : {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL}) { - if (traits.supports_swing_mode(swing_mode)) - msg.supported_swing_modes.push_back(static_cast(swing_mode)); - } + for (auto swing_mode : traits.get_supported_swing_modes()) + msg.supported_swing_modes.push_back(static_cast(swing_mode)); return this->send_list_entities_climate_response(msg); } void APIConnection::climate_command(const ClimateCommandRequest &msg) { @@ -548,8 +534,8 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { call.set_target_temperature_low(msg.target_temperature_low); if (msg.has_target_temperature_high) call.set_target_temperature_high(msg.target_temperature_high); - if (msg.has_away) - call.set_away(msg.away); + if (msg.has_legacy_away) + call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME); if (msg.has_fan_mode) call.set_fan_mode(static_cast(msg.fan_mode)); if (msg.has_custom_fan_mode) diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index a9e9d64bc1..1e023f3988 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,16 +192,16 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { - case enums::CLIMATE_PRESET_ECO: - return "CLIMATE_PRESET_ECO"; + case enums::CLIMATE_PRESET_HOME: + return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: return "CLIMATE_PRESET_AWAY"; case enums::CLIMATE_PRESET_BOOST: return "CLIMATE_PRESET_BOOST"; case enums::CLIMATE_PRESET_COMFORT: return "CLIMATE_PRESET_COMFORT"; - case enums::CLIMATE_PRESET_HOME: - return "CLIMATE_PRESET_HOME"; + case enums::CLIMATE_PRESET_ECO: + return "CLIMATE_PRESET_ECO"; case enums::CLIMATE_PRESET_SLEEP: return "CLIMATE_PRESET_SLEEP"; case enums::CLIMATE_PRESET_ACTIVITY: @@ -2672,7 +2672,7 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v return true; } case 11: { - this->supports_away = value.as_bool(); + this->legacy_supports_away = value.as_bool(); return true; } case 12: { @@ -2756,7 +2756,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(8, this->visual_min_temperature); buffer.encode_float(9, this->visual_max_temperature); buffer.encode_float(10, this->visual_temperature_step); - buffer.encode_bool(11, this->supports_away); + buffer.encode_bool(11, this->legacy_supports_away); buffer.encode_bool(12, this->supports_action); for (auto &it : this->supported_fan_modes) { buffer.encode_enum(13, it, true); @@ -2823,8 +2823,8 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" supports_away: "); - out.append(YESNO(this->supports_away)); + out.append(" legacy_supports_away: "); + out.append(YESNO(this->legacy_supports_away)); out.append("\n"); out.append(" supports_action: "); @@ -2869,7 +2869,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { return true; } case 7: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 8: { @@ -2939,7 +2939,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(4, this->target_temperature); buffer.encode_float(5, this->target_temperature_low); buffer.encode_float(6, this->target_temperature_high); - buffer.encode_bool(7, this->away); + buffer.encode_bool(7, this->legacy_away); buffer.encode_enum(8, this->action); buffer.encode_enum(9, this->fan_mode); buffer.encode_enum(10, this->swing_mode); @@ -2979,8 +2979,8 @@ void ClimateStateResponse::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" action: "); @@ -3031,11 +3031,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) return true; } case 10: { - this->has_away = value.as_bool(); + this->has_legacy_away = value.as_bool(); return true; } case 11: { - this->away = value.as_bool(); + this->legacy_away = value.as_bool(); return true; } case 12: { @@ -3120,8 +3120,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_float(7, this->target_temperature_low); buffer.encode_bool(8, this->has_target_temperature_high); buffer.encode_float(9, this->target_temperature_high); - buffer.encode_bool(10, this->has_away); - buffer.encode_bool(11, this->away); + buffer.encode_bool(10, this->has_legacy_away); + buffer.encode_bool(11, this->legacy_away); buffer.encode_bool(12, this->has_fan_mode); buffer.encode_enum(13, this->fan_mode); buffer.encode_bool(14, this->has_swing_mode); @@ -3176,12 +3176,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const { out.append(buffer); out.append("\n"); - out.append(" has_away: "); - out.append(YESNO(this->has_away)); + out.append(" has_legacy_away: "); + out.append(YESNO(this->has_legacy_away)); out.append("\n"); - out.append(" away: "); - out.append(YESNO(this->away)); + out.append(" legacy_away: "); + out.append(YESNO(this->legacy_away)); out.append("\n"); out.append(" has_fan_mode: "); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 04d5834572..7f37c0b94b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,11 +90,11 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_ECO = 0, + CLIMATE_PRESET_HOME = 0, CLIMATE_PRESET_AWAY = 1, CLIMATE_PRESET_BOOST = 2, CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_HOME = 4, + CLIMATE_PRESET_ECO = 4, CLIMATE_PRESET_SLEEP = 5, CLIMATE_PRESET_ACTIVITY = 6, }; @@ -709,7 +709,7 @@ class ListEntitiesClimateResponse : public ProtoMessage { float visual_min_temperature{0.0f}; float visual_max_temperature{0.0f}; float visual_temperature_step{0.0f}; - bool supports_away{false}; + bool legacy_supports_away{false}; bool supports_action{false}; std::vector supported_fan_modes{}; std::vector supported_swing_modes{}; @@ -732,7 +732,7 @@ class ClimateStateResponse : public ProtoMessage { float target_temperature{0.0f}; float target_temperature_low{0.0f}; float target_temperature_high{0.0f}; - bool away{false}; + bool legacy_away{false}; enums::ClimateAction action{}; enums::ClimateFanMode fan_mode{}; enums::ClimateSwingMode swing_mode{}; @@ -758,8 +758,8 @@ class ClimateCommandRequest : public ProtoMessage { float target_temperature_low{0.0f}; bool has_target_temperature_high{false}; float target_temperature_high{0.0f}; - bool has_away{false}; - bool away{false}; + bool has_legacy_away{false}; + bool legacy_away{false}; bool has_fan_mode{false}; enums::ClimateFanMode fan_mode{}; bool has_swing_mode{false}; diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index ea1442755d..c915d69981 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -32,8 +32,8 @@ void BangBangClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) - this->change_away_(*call.get_away()); + if (call.get_preset().has_value()) + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); this->compute_state_(); this->publish_state(); @@ -41,11 +41,20 @@ void BangBangClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits BangBangClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + }); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); traits.set_supports_two_point_target_temperature(true); - traits.set_supports_away(this->supports_away_); + if (supports_away_) + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_AWAY, + }); traits.set_supports_action(true); return traits; } @@ -140,7 +149,7 @@ void BangBangClimate::change_away_(bool away) { this->target_temperature_low = this->away_config_.default_temperature_low; this->target_temperature_high = this->away_config_.default_temperature_high; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void BangBangClimate::set_normal_config(const BangBangClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/climate/automation.h b/esphome/components/climate/automation.h index b0b71cb7d7..49a87027f2 100644 --- a/esphome/components/climate/automation.h +++ b/esphome/components/climate/automation.h @@ -27,7 +27,9 @@ template class ControlAction : public Action { call.set_target_temperature(this->target_temperature_.optional_value(x...)); call.set_target_temperature_low(this->target_temperature_low_.optional_value(x...)); call.set_target_temperature_high(this->target_temperature_high_.optional_value(x...)); - call.set_away(this->away_.optional_value(x...)); + if (away_.has_value()) { + call.set_preset(away_.value(x...) ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME); + } call.set_fan_mode(this->fan_mode_.optional_value(x...)); call.set_fan_mode(this->custom_fan_mode_.optional_value(x...)); call.set_preset(this->preset_.optional_value(x...)); diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index c047d96cdb..5369449839 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -43,9 +43,6 @@ void ClimateCall::perform() { if (this->target_temperature_high_.has_value()) { ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_); } - if (this->away_.has_value()) { - ESP_LOGD(TAG, " Away Mode: %s", ONOFF(*this->away_)); - } this->parent_->control(*this); } void ClimateCall::validate_() { @@ -125,12 +122,6 @@ void ClimateCall::validate_() { this->target_temperature_high_.reset(); } } - if (this->away_.has_value()) { - if (!traits.get_supports_away()) { - ESP_LOGW(TAG, " Cannot set away mode for this device!"); - this->away_.reset(); - } - } } ClimateCall &ClimateCall::set_mode(ClimateMode mode) { this->mode_ = mode; @@ -181,8 +172,7 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) { } else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) { this->set_fan_mode(CLIMATE_FAN_DIFFUSE); } else { - auto custom_fan_modes = this->parent_->get_traits().get_supported_custom_fan_modes(); - if (std::find(custom_fan_modes.begin(), custom_fan_modes.end(), fan_mode) != custom_fan_modes.end()) { + if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) { this->custom_fan_mode_ = fan_mode; this->fan_mode_.reset(); } else { @@ -218,8 +208,7 @@ ClimateCall &ClimateCall::set_preset(const std::string &preset) { } else if (str_equals_case_insensitive(preset, "ACTIVITY")) { this->set_preset(CLIMATE_PRESET_ACTIVITY); } else { - auto custom_presets = this->parent_->get_traits().get_supported_custom_presets(); - if (std::find(custom_presets.begin(), custom_presets.end(), preset) != custom_presets.end()) { + if (this->parent_->get_traits().supports_custom_preset(preset)) { this->custom_preset_ = preset; this->preset_.reset(); } else { @@ -269,18 +258,23 @@ const optional &ClimateCall::get_mode() const { return this->mode_; const optional &ClimateCall::get_target_temperature() const { return this->target_temperature_; } const optional &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; } const optional &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; } -const optional &ClimateCall::get_away() const { return this->away_; } +optional ClimateCall::get_away() const { + if (!this->preset_.has_value()) + return {}; + return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY; +} const optional &ClimateCall::get_fan_mode() const { return this->fan_mode_; } const optional &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; } const optional &ClimateCall::get_preset() const { return this->preset_; } const optional &ClimateCall::get_custom_preset() const { return this->custom_preset_; } const optional &ClimateCall::get_swing_mode() const { return this->swing_mode_; } ClimateCall &ClimateCall::set_away(bool away) { - this->away_ = away; + this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_away(optional away) { - this->away_ = away; + if (away.has_value()) + this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME; return *this; } ClimateCall &ClimateCall::set_target_temperature_high(optional target_temperature_high) { @@ -338,20 +332,17 @@ void Climate::save_state_() { } else { state.target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - state.away = this->away; - } if (traits.get_supports_fan_modes() && fan_mode.has_value()) { state.uses_custom_fan_mode = false; state.fan_mode = this->fan_mode.value(); } if (!traits.get_supported_custom_fan_modes().empty() && custom_fan_mode.has_value()) { state.uses_custom_fan_mode = true; - auto &custom_fan_modes = traits.get_supported_custom_fan_modes(); - auto it = std::find(custom_fan_modes.begin(), custom_fan_modes.end(), this->custom_fan_mode.value()); - // only set custom fan mode if value exists, otherwise leave it as is - if (it != custom_fan_modes.cend()) { - state.custom_fan_mode = std::distance(custom_fan_modes.begin(), it); + const auto &supported = traits.get_supported_custom_fan_modes(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_fan_mode); + if (it != vec.end()) { + state.custom_fan_mode = std::distance(vec.begin(), it); } } if (traits.get_supports_presets() && preset.has_value()) { @@ -360,11 +351,12 @@ void Climate::save_state_() { } if (!traits.get_supported_custom_presets().empty() && custom_preset.has_value()) { state.uses_custom_preset = true; - auto custom_presets = traits.get_supported_custom_presets(); - auto it = std::find(custom_presets.begin(), custom_presets.end(), this->custom_preset.value()); + const auto &supported = traits.get_supported_custom_presets(); + std::vector vec{supported.begin(), supported.end()}; + auto it = std::find(vec.begin(), vec.end(), custom_preset); // only set custom preset if value exists, otherwise leave it as is - if (it != custom_presets.cend()) { - state.custom_preset = std::distance(custom_presets.begin(), it); + if (it != vec.cend()) { + state.custom_preset = std::distance(vec.begin(), it); } } if (traits.get_supports_swing_modes()) { @@ -405,9 +397,6 @@ void Climate::publish_state() { } else { ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature); } - if (traits.get_supports_away()) { - ESP_LOGD(TAG, " Away: %s", ONOFF(this->away)); - } // Send state to frontend this->state_callback_.call(); @@ -453,9 +442,6 @@ ClimateCall ClimateDeviceRestoreState::to_call(Climate *climate) { } else { call.set_target_temperature(this->target_temperature); } - if (traits.get_supports_away()) { - call.set_away(this->away); - } if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { call.set_fan_mode(this->fan_mode); } @@ -476,23 +462,27 @@ void ClimateDeviceRestoreState::apply(Climate *climate) { } else { climate->target_temperature = this->target_temperature; } - if (traits.get_supports_away()) { - climate->away = this->away; - } if (traits.get_supports_fan_modes() && !this->uses_custom_fan_mode) { climate->fan_mode = this->fan_mode; } if (!traits.get_supported_custom_fan_modes().empty() && this->uses_custom_fan_mode) { - climate->custom_fan_mode = traits.get_supported_custom_fan_modes()[this->custom_fan_mode]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &modes = traits.get_supported_custom_fan_modes(); + std::vector modes_vec{modes.begin(), modes.end()}; + if (custom_fan_mode < modes_vec.size()) { + climate->custom_fan_mode = modes_vec[this->custom_fan_mode]; + } } if (traits.get_supports_presets() && !this->uses_custom_preset) { climate->preset = this->preset; } - if (!traits.get_supported_custom_presets().empty() && this->uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->custom_preset]; - } if (!traits.get_supported_custom_presets().empty() && uses_custom_preset) { - climate->custom_preset = traits.get_supported_custom_presets()[this->preset]; + // std::set has consistent order (lexicographic for strings), so this is ok + const auto &presets = traits.get_supported_custom_presets(); + std::vector presets_vec{presets.begin(), presets.end()}; + if (custom_preset < presets_vec.size()) { + climate->custom_preset = presets_vec[this->custom_preset]; + } } if (traits.get_supports_swing_modes()) { climate->swing_mode = this->swing_mode; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index 3aa29be2fb..4b12c98dc7 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -63,7 +63,9 @@ class ClimateCall { * For climate devices with two point target temperature control */ ClimateCall &set_target_temperature_high(optional target_temperature_high); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(bool away); + ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead") ClimateCall &set_away(optional away); /// Set the fan mode of the climate device. ClimateCall &set_fan_mode(ClimateFanMode fan_mode); @@ -94,7 +96,8 @@ class ClimateCall { const optional &get_target_temperature() const; const optional &get_target_temperature_low() const; const optional &get_target_temperature_high() const; - const optional &get_away() const; + ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead") + optional get_away() const; const optional &get_fan_mode() const; const optional &get_swing_mode() const; const optional &get_custom_fan_mode() const; @@ -109,7 +112,6 @@ class ClimateCall { optional target_temperature_; optional target_temperature_low_; optional target_temperature_high_; - optional away_; optional fan_mode_; optional swing_mode_; optional custom_fan_mode_; @@ -120,7 +122,6 @@ class ClimateCall { /// Struct used to save the state of the climate device in restore memory. struct ClimateDeviceRestoreState { ClimateMode mode; - bool away; bool uses_custom_fan_mode{false}; union { ClimateFanMode fan_mode; @@ -191,6 +192,7 @@ class Climate : public Nameable { * Away allows climate devices to have two different target temperature configs: * one for normal mode and one for away mode. */ + ESPDEPRECATED("away is deprecated, use preset instead") bool away{false}; /// The active fan mode of the climate device. diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 4540208a3f..099074a887 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_HOME: + return "HOME"; case climate::CLIMATE_PRESET_ECO: return "ECO"; case climate::CLIMATE_PRESET_AWAY: @@ -92,8 +94,6 @@ const char *climate_preset_to_string(ClimatePreset preset) { return "BOOST"; case climate::CLIMATE_PRESET_COMFORT: return "COMFORT"; - case climate::CLIMATE_PRESET_HOME: - return "HOME"; case climate::CLIMATE_PRESET_SLEEP: return "SLEEP"; case climate::CLIMATE_PRESET_ACTIVITY: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index e129fca91d..7afa2dae55 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -39,7 +39,6 @@ enum ClimateAction : uint8_t { CLIMATE_ACTION_FAN = 6, }; -/// Enum for all modes a climate fan can be in enum ClimateFanMode : uint8_t { /// The fan mode is set to On CLIMATE_FAN_ON = 0, @@ -75,16 +74,16 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 0, + /// Preset is set to HOME + CLIMATE_PRESET_HOME = 0, /// Preset is set to AWAY CLIMATE_PRESET_AWAY = 1, /// Preset is set to BOOST CLIMATE_PRESET_BOOST = 2, /// Preset is set to COMFORT CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 4, + /// Preset is set to ECO + CLIMATE_PRESET_ECO = 4, /// Preset is set to SLEEP CLIMATE_PRESET_SLEEP = 5, /// Preset is set to ACTIVITY diff --git a/esphome/components/climate/climate_traits.cpp b/esphome/components/climate/climate_traits.cpp index 774ada785f..c871552360 100644 --- a/esphome/components/climate/climate_traits.cpp +++ b/esphome/components/climate/climate_traits.cpp @@ -4,55 +4,6 @@ namespace esphome { namespace climate { -bool ClimateTraits::supports_mode(ClimateMode mode) const { - switch (mode) { - case CLIMATE_MODE_OFF: - return true; - case CLIMATE_MODE_HEAT_COOL: - return this->supports_heat_cool_mode_; - case CLIMATE_MODE_AUTO: - return this->supports_auto_mode_; - case CLIMATE_MODE_COOL: - return this->supports_cool_mode_; - case CLIMATE_MODE_HEAT: - return this->supports_heat_mode_; - case CLIMATE_MODE_FAN_ONLY: - return this->supports_fan_only_mode_; - case CLIMATE_MODE_DRY: - return this->supports_dry_mode_; - default: - return false; - } -} -bool ClimateTraits::get_supports_current_temperature() const { return supports_current_temperature_; } -void ClimateTraits::set_supports_current_temperature(bool supports_current_temperature) { - supports_current_temperature_ = supports_current_temperature; -} -bool ClimateTraits::get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } -void ClimateTraits::set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { - supports_two_point_target_temperature_ = supports_two_point_target_temperature; -} -void ClimateTraits::set_supports_auto_mode(bool supports_auto_mode) { supports_auto_mode_ = supports_auto_mode; } -void ClimateTraits::set_supports_heat_cool_mode(bool supports_heat_cool_mode) { - supports_heat_cool_mode_ = supports_heat_cool_mode; -} -void ClimateTraits::set_supports_cool_mode(bool supports_cool_mode) { supports_cool_mode_ = supports_cool_mode; } -void ClimateTraits::set_supports_heat_mode(bool supports_heat_mode) { supports_heat_mode_ = supports_heat_mode; } -void ClimateTraits::set_supports_fan_only_mode(bool supports_fan_only_mode) { - supports_fan_only_mode_ = supports_fan_only_mode; -} -void ClimateTraits::set_supports_dry_mode(bool supports_dry_mode) { supports_dry_mode_ = supports_dry_mode; } -void ClimateTraits::set_supports_away(bool supports_away) { supports_away_ = supports_away; } -void ClimateTraits::set_supports_action(bool supports_action) { supports_action_ = supports_action; } -float ClimateTraits::get_visual_min_temperature() const { return visual_min_temperature_; } -void ClimateTraits::set_visual_min_temperature(float visual_min_temperature) { - visual_min_temperature_ = visual_min_temperature; -} -float ClimateTraits::get_visual_max_temperature() const { return visual_max_temperature_; } -void ClimateTraits::set_visual_max_temperature(float visual_max_temperature) { - visual_max_temperature_ = visual_max_temperature; -} -float ClimateTraits::get_visual_temperature_step() const { return visual_temperature_step_; } int8_t ClimateTraits::get_temperature_accuracy_decimals() const { // use printf %g to find number of digits based on temperature step char buf[32]; @@ -64,160 +15,6 @@ int8_t ClimateTraits::get_temperature_accuracy_decimals() const { return str.length() - dot_pos - 1; } -void ClimateTraits::set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } -bool ClimateTraits::get_supports_away() const { return supports_away_; } -bool ClimateTraits::get_supports_action() const { return supports_action_; } -void ClimateTraits::set_supports_fan_mode_on(bool supports_fan_mode_on) { - this->supports_fan_mode_on_ = supports_fan_mode_on; -} -void ClimateTraits::set_supports_fan_mode_off(bool supports_fan_mode_off) { - this->supports_fan_mode_off_ = supports_fan_mode_off; -} -void ClimateTraits::set_supports_fan_mode_auto(bool supports_fan_mode_auto) { - this->supports_fan_mode_auto_ = supports_fan_mode_auto; -} -void ClimateTraits::set_supports_fan_mode_low(bool supports_fan_mode_low) { - this->supports_fan_mode_low_ = supports_fan_mode_low; -} -void ClimateTraits::set_supports_fan_mode_medium(bool supports_fan_mode_medium) { - this->supports_fan_mode_medium_ = supports_fan_mode_medium; -} -void ClimateTraits::set_supports_fan_mode_high(bool supports_fan_mode_high) { - this->supports_fan_mode_high_ = supports_fan_mode_high; -} -void ClimateTraits::set_supports_fan_mode_middle(bool supports_fan_mode_middle) { - this->supports_fan_mode_middle_ = supports_fan_mode_middle; -} -void ClimateTraits::set_supports_fan_mode_focus(bool supports_fan_mode_focus) { - this->supports_fan_mode_focus_ = supports_fan_mode_focus; -} -void ClimateTraits::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) { - this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse; -} -bool ClimateTraits::supports_fan_mode(ClimateFanMode fan_mode) const { - switch (fan_mode) { - case climate::CLIMATE_FAN_ON: - return this->supports_fan_mode_on_; - case climate::CLIMATE_FAN_OFF: - return this->supports_fan_mode_off_; - case climate::CLIMATE_FAN_AUTO: - return this->supports_fan_mode_auto_; - case climate::CLIMATE_FAN_LOW: - return this->supports_fan_mode_low_; - case climate::CLIMATE_FAN_MEDIUM: - return this->supports_fan_mode_medium_; - case climate::CLIMATE_FAN_HIGH: - return this->supports_fan_mode_high_; - case climate::CLIMATE_FAN_MIDDLE: - return this->supports_fan_mode_middle_; - case climate::CLIMATE_FAN_FOCUS: - return this->supports_fan_mode_focus_; - case climate::CLIMATE_FAN_DIFFUSE: - return this->supports_fan_mode_diffuse_; - default: - return false; - } -} -bool ClimateTraits::get_supports_fan_modes() const { - return this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ || - this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ || - this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_; -} -void ClimateTraits::set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes) { - this->supported_custom_fan_modes_ = supported_custom_fan_modes; -} -const std::vector ClimateTraits::get_supported_custom_fan_modes() const { - return this->supported_custom_fan_modes_; -} -bool ClimateTraits::supports_custom_fan_mode(std::string &custom_fan_mode) const { - return std::count(this->supported_custom_fan_modes_.begin(), this->supported_custom_fan_modes_.end(), - custom_fan_mode); -} -bool ClimateTraits::supports_preset(ClimatePreset preset) const { - switch (preset) { - case climate::CLIMATE_PRESET_ECO: - return this->supports_preset_eco_; - case climate::CLIMATE_PRESET_AWAY: - return this->supports_preset_away_; - case climate::CLIMATE_PRESET_BOOST: - return this->supports_preset_boost_; - case climate::CLIMATE_PRESET_COMFORT: - return this->supports_preset_comfort_; - case climate::CLIMATE_PRESET_HOME: - return this->supports_preset_home_; - case climate::CLIMATE_PRESET_SLEEP: - return this->supports_preset_sleep_; - case climate::CLIMATE_PRESET_ACTIVITY: - return this->supports_preset_activity_; - default: - return false; - } -} -void ClimateTraits::set_supports_preset_eco(bool supports_preset_eco) { - this->supports_preset_eco_ = supports_preset_eco; -} -void ClimateTraits::set_supports_preset_away(bool supports_preset_away) { - this->supports_preset_away_ = supports_preset_away; -} -void ClimateTraits::set_supports_preset_boost(bool supports_preset_boost) { - this->supports_preset_boost_ = supports_preset_boost; -} -void ClimateTraits::set_supports_preset_comfort(bool supports_preset_comfort) { - this->supports_preset_comfort_ = supports_preset_comfort; -} -void ClimateTraits::set_supports_preset_home(bool supports_preset_home) { - this->supports_preset_home_ = supports_preset_home; -} -void ClimateTraits::set_supports_preset_sleep(bool supports_preset_sleep) { - this->supports_preset_sleep_ = supports_preset_sleep; -} -void ClimateTraits::set_supports_preset_activity(bool supports_preset_activity) { - this->supports_preset_activity_ = supports_preset_activity; -} -bool ClimateTraits::get_supports_presets() const { - return this->supports_preset_eco_ || this->supports_preset_away_ || this->supports_preset_boost_ || - this->supports_preset_comfort_ || this->supports_preset_home_ || this->supports_preset_sleep_ || - this->supports_preset_activity_; -} -void ClimateTraits::set_supported_custom_presets(std::vector &supported_custom_presets) { - this->supported_custom_presets_ = supported_custom_presets; -} -const std::vector ClimateTraits::get_supported_custom_presets() const { - return this->supported_custom_presets_; -} -bool ClimateTraits::supports_custom_preset(std::string &custom_preset) const { - return std::count(this->supported_custom_presets_.begin(), this->supported_custom_presets_.end(), custom_preset); -} -void ClimateTraits::set_supports_swing_mode_off(bool supports_swing_mode_off) { - this->supports_swing_mode_off_ = supports_swing_mode_off; -} -void ClimateTraits::set_supports_swing_mode_both(bool supports_swing_mode_both) { - this->supports_swing_mode_both_ = supports_swing_mode_both; -} -void ClimateTraits::set_supports_swing_mode_vertical(bool supports_swing_mode_vertical) { - this->supports_swing_mode_vertical_ = supports_swing_mode_vertical; -} -void ClimateTraits::set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal) { - this->supports_swing_mode_horizontal_ = supports_swing_mode_horizontal; -} -bool ClimateTraits::supports_swing_mode(ClimateSwingMode swing_mode) const { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - return this->supports_swing_mode_off_; - case climate::CLIMATE_SWING_BOTH: - return this->supports_swing_mode_both_; - case climate::CLIMATE_SWING_VERTICAL: - return this->supports_swing_mode_vertical_; - case climate::CLIMATE_SWING_HORIZONTAL: - return this->supports_swing_mode_horizontal_; - default: - return false; - } -} -bool ClimateTraits::get_supports_swing_modes() const { - return this->supports_swing_mode_off_ || this->supports_swing_mode_both_ || supports_swing_mode_vertical_ || - supports_swing_mode_horizontal_; -} } // namespace climate } // namespace esphome diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index f8e6f87306..b86a0c7774 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -2,6 +2,7 @@ #include "esphome/core/helpers.h" #include "climate_mode.h" +#include namespace esphome { namespace climate { @@ -24,8 +25,6 @@ namespace climate { * - heat mode (increases current temperature) * - dry mode (removes humidity from air) * - fan mode (only turns on fan) - * - supports away - away mode means that the climate device supports two different - * target temperature settings: one target temp setting for "away" mode and one for non-away mode. * - supports action - if the climate device supports reporting the active * current action of the device with the action property. * - supports fan modes - optionally, if it has a fan which can be configured in different ways: @@ -41,95 +40,147 @@ namespace climate { */ class ClimateTraits { public: - bool get_supports_current_temperature() const; - void set_supports_current_temperature(bool supports_current_temperature); - bool get_supports_two_point_target_temperature() const; - void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature); - void set_supports_auto_mode(bool supports_auto_mode); - void set_supports_heat_cool_mode(bool supports_heat_cool_mode); - void set_supports_cool_mode(bool supports_cool_mode); - void set_supports_heat_mode(bool supports_heat_mode); - void set_supports_fan_only_mode(bool supports_fan_only_mode); - void set_supports_dry_mode(bool supports_dry_mode); - void set_supports_away(bool supports_away); - bool get_supports_away() const; - void set_supports_action(bool supports_action); - bool get_supports_action() const; - bool supports_mode(ClimateMode mode) const; - void set_supports_fan_mode_on(bool supports_fan_mode_on); - void set_supports_fan_mode_off(bool supports_fan_mode_off); - void set_supports_fan_mode_auto(bool supports_fan_mode_auto); - void set_supports_fan_mode_low(bool supports_fan_mode_low); - void set_supports_fan_mode_medium(bool supports_fan_mode_medium); - void set_supports_fan_mode_high(bool supports_fan_mode_high); - void set_supports_fan_mode_middle(bool supports_fan_mode_middle); - void set_supports_fan_mode_focus(bool supports_fan_mode_focus); - void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse); - bool supports_fan_mode(ClimateFanMode fan_mode) const; - bool get_supports_fan_modes() const; - void set_supported_custom_fan_modes(std::vector &supported_custom_fan_modes); - const std::vector get_supported_custom_fan_modes() const; - bool supports_custom_fan_mode(std::string &custom_fan_mode) const; - bool supports_preset(ClimatePreset preset) const; - void set_supports_preset_eco(bool supports_preset_eco); - void set_supports_preset_away(bool supports_preset_away); - void set_supports_preset_boost(bool supports_preset_boost); - void set_supports_preset_comfort(bool supports_preset_comfort); - void set_supports_preset_home(bool supports_preset_home); - void set_supports_preset_sleep(bool supports_preset_sleep); - void set_supports_preset_activity(bool supports_preset_activity); - bool get_supports_presets() const; - void set_supported_custom_presets(std::vector &supported_custom_presets); - const std::vector get_supported_custom_presets() const; - bool supports_custom_preset(std::string &custom_preset) const; - void set_supports_swing_mode_off(bool supports_swing_mode_off); - void set_supports_swing_mode_both(bool supports_swing_mode_both); - void set_supports_swing_mode_vertical(bool supports_swing_mode_vertical); - void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal); - bool supports_swing_mode(ClimateSwingMode swing_mode) const; - bool get_supports_swing_modes() const; + bool get_supports_current_temperature() const { return supports_current_temperature_; } + void set_supports_current_temperature(bool supports_current_temperature) { + supports_current_temperature_ = supports_current_temperature; + } + bool get_supports_two_point_target_temperature() const { return supports_two_point_target_temperature_; } + void set_supports_two_point_target_temperature(bool supports_two_point_target_temperature) { + supports_two_point_target_temperature_ = supports_two_point_target_temperature; + } + void set_supported_modes(std::set modes) { supported_modes_ = std::move(modes); } + void add_supported_mode(ClimateMode mode) { supported_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_auto_mode(bool supports_auto_mode) { set_mode_support_(CLIMATE_MODE_AUTO, supports_auto_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_cool_mode(bool supports_cool_mode) { set_mode_support_(CLIMATE_MODE_COOL, supports_cool_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_mode(bool supports_heat_mode) { set_mode_support_(CLIMATE_MODE_HEAT, supports_heat_mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_heat_cool_mode(bool supported) { set_mode_support_(CLIMATE_MODE_HEAT_COOL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_fan_only_mode(bool supports_fan_only_mode) { + set_mode_support_(CLIMATE_MODE_FAN_ONLY, supports_fan_only_mode); + } + ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead") + void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } + bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } + const std::set get_supported_modes() const { return supported_modes_; } - float get_visual_min_temperature() const; - void set_visual_min_temperature(float visual_min_temperature); - float get_visual_max_temperature() const; - void set_visual_max_temperature(float visual_max_temperature); - float get_visual_temperature_step() const; + void set_supports_action(bool supports_action) { supports_action_ = supports_action; } + bool get_supports_action() const { return supports_action_; } + + void set_supported_fan_modes(std::set modes) { supported_fan_modes_ = std::move(modes); } + void add_supported_fan_mode(ClimateFanMode mode) { supported_fan_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_on(bool supported) { set_fan_mode_support_(CLIMATE_FAN_ON, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_off(bool supported) { set_fan_mode_support_(CLIMATE_FAN_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_auto(bool supported) { set_fan_mode_support_(CLIMATE_FAN_AUTO, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_low(bool supported) { set_fan_mode_support_(CLIMATE_FAN_LOW, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_medium(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MEDIUM, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_high(bool supported) { set_fan_mode_support_(CLIMATE_FAN_HIGH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_middle(bool supported) { set_fan_mode_support_(CLIMATE_FAN_MIDDLE, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_focus(bool supported) { set_fan_mode_support_(CLIMATE_FAN_FOCUS, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } + bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } + bool get_supports_fan_modes() const { return !supported_fan_modes_.empty(); } + const std::set get_supported_fan_modes() const { return supported_fan_modes_; } + + void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { + supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); + } + const std::set &get_supported_custom_fan_modes() const { return supported_custom_fan_modes_; } + bool supports_custom_fan_mode(const std::string &custom_fan_mode) const { + return supported_custom_fan_modes_.count(custom_fan_mode); + } + + void set_supported_presets(std::set presets) { supported_presets_ = std::move(presets); } + void add_supported_preset(ClimatePreset preset) { supported_presets_.insert(preset); } + bool supports_preset(ClimatePreset preset) const { return supported_presets_.count(preset); } + bool get_supports_presets() const { return !supported_presets_.empty(); } + const std::set &get_supported_presets() const { return supported_presets_; } + + void set_supported_custom_presets(std::set supported_custom_presets) { + supported_custom_presets_ = std::move(supported_custom_presets); + } + const std::set &get_supported_custom_presets() const { return supported_custom_presets_; } + bool supports_custom_preset(const std::string &custom_preset) const { + return supported_custom_presets_.count(custom_preset); + } + ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead") + void set_supports_away(bool supports) { + if (supports) { + supported_presets_.insert(CLIMATE_PRESET_AWAY); + supported_presets_.insert(CLIMATE_PRESET_HOME); + } + } + ESPDEPRECATED("This method is deprecated, use supports_preset() instead") + bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); } + + void set_supported_swing_modes(std::set modes) { supported_swing_modes_ = std::move(modes); } + void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_off(bool supported) { set_swing_mode_support_(CLIMATE_SWING_OFF, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_both(bool supported) { set_swing_mode_support_(CLIMATE_SWING_BOTH, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_vertical(bool supported) { set_swing_mode_support_(CLIMATE_SWING_VERTICAL, supported); } + ESPDEPRECATED("This method is deprecated, use set_supported_fan_modes() instead") + void set_supports_swing_mode_horizontal(bool supported) { + set_swing_mode_support_(CLIMATE_SWING_HORIZONTAL, supported); + } + bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } + bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } + const std::set get_supported_swing_modes() { return supported_swing_modes_; } + + float get_visual_min_temperature() const { return visual_min_temperature_; } + void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } + float get_visual_max_temperature() const { return visual_max_temperature_; } + void set_visual_max_temperature(float visual_max_temperature) { visual_max_temperature_ = visual_max_temperature; } + float get_visual_temperature_step() const { return visual_temperature_step_; } int8_t get_temperature_accuracy_decimals() const; - void set_visual_temperature_step(float temperature_step); + void set_visual_temperature_step(float temperature_step) { visual_temperature_step_ = temperature_step; } protected: + void set_mode_support_(climate::ClimateMode mode, bool supported) { + if (supported) { + supported_modes_.insert(mode); + } else { + supported_modes_.erase(mode); + } + } + void set_fan_mode_support_(climate::ClimateFanMode mode, bool supported) { + if (supported) { + supported_fan_modes_.insert(mode); + } else { + supported_fan_modes_.erase(mode); + } + } + void set_swing_mode_support_(climate::ClimateSwingMode mode, bool supported) { + if (supported) { + supported_swing_modes_.insert(mode); + } else { + supported_swing_modes_.erase(mode); + } + } + bool supports_current_temperature_{false}; bool supports_two_point_target_temperature_{false}; - bool supports_auto_mode_{false}; - bool supports_heat_cool_mode_{false}; - bool supports_cool_mode_{false}; - bool supports_heat_mode_{false}; - bool supports_fan_only_mode_{false}; - bool supports_dry_mode_{false}; - bool supports_away_{false}; + std::set supported_modes_ = {climate::CLIMATE_MODE_OFF}; bool supports_action_{false}; - bool supports_fan_mode_on_{false}; - bool supports_fan_mode_off_{false}; - bool supports_fan_mode_auto_{false}; - bool supports_fan_mode_low_{false}; - bool supports_fan_mode_medium_{false}; - bool supports_fan_mode_high_{false}; - bool supports_fan_mode_middle_{false}; - bool supports_fan_mode_focus_{false}; - bool supports_fan_mode_diffuse_{false}; - bool supports_swing_mode_off_{false}; - bool supports_swing_mode_both_{false}; - bool supports_swing_mode_vertical_{false}; - bool supports_swing_mode_horizontal_{false}; - bool supports_preset_eco_{false}; - bool supports_preset_away_{false}; - bool supports_preset_boost_{false}; - bool supports_preset_comfort_{false}; - bool supports_preset_home_{false}; - bool supports_preset_sleep_{false}; - bool supports_preset_activity_{false}; - std::vector supported_custom_fan_modes_; - std::vector supported_custom_presets_; + std::set supported_fan_modes_; + std::set supported_swing_modes_; + std::set supported_presets_; + std::set supported_custom_fan_modes_; + std::set supported_custom_presets_; float visual_min_temperature_{10}; float visual_max_temperature_{30}; diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index f88b2174ee..16a8e78414 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -9,63 +9,22 @@ static const char *TAG = "climate_ir"; climate::ClimateTraits ClimateIR::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(this->minimum_temperature_); traits.set_visual_max_temperature(this->maximum_temperature_); traits.set_visual_temperature_step(this->temperature_step_); - for (auto fan_mode : this->fan_modes_) { - switch (fan_mode) { - case climate::CLIMATE_FAN_AUTO: - traits.set_supports_fan_mode_auto(true); - break; - case climate::CLIMATE_FAN_DIFFUSE: - traits.set_supports_fan_mode_diffuse(true); - break; - case climate::CLIMATE_FAN_FOCUS: - traits.set_supports_fan_mode_focus(true); - break; - case climate::CLIMATE_FAN_HIGH: - traits.set_supports_fan_mode_high(true); - break; - case climate::CLIMATE_FAN_LOW: - traits.set_supports_fan_mode_low(true); - break; - case climate::CLIMATE_FAN_MEDIUM: - traits.set_supports_fan_mode_medium(true); - break; - case climate::CLIMATE_FAN_MIDDLE: - traits.set_supports_fan_mode_middle(true); - break; - case climate::CLIMATE_FAN_OFF: - traits.set_supports_fan_mode_off(true); - break; - case climate::CLIMATE_FAN_ON: - traits.set_supports_fan_mode_on(true); - break; - } - } - for (auto swing_mode : this->swing_modes_) { - switch (swing_mode) { - case climate::CLIMATE_SWING_OFF: - traits.set_supports_swing_mode_off(true); - break; - case climate::CLIMATE_SWING_BOTH: - traits.set_supports_swing_mode_both(true); - break; - case climate::CLIMATE_SWING_VERTICAL: - traits.set_supports_swing_mode_vertical(true); - break; - case climate::CLIMATE_SWING_HORIZONTAL: - traits.set_supports_swing_mode_horizontal(true); - break; - } - } + traits.set_supported_fan_modes(fan_modes_); + traits.set_supported_swing_modes(swing_modes_); return traits; } diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 7a69b19786..ff04fa4e2f 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -19,9 +19,8 @@ namespace climate_ir { class ClimateIR : public climate::Climate, public Component, public remote_base::RemoteReceiverListener { public: ClimateIR(float minimum_temperature, float maximum_temperature, float temperature_step = 1.0f, - bool supports_dry = false, bool supports_fan_only = false, - std::vector fan_modes = {}, - std::vector swing_modes = {}) { + bool supports_dry = false, bool supports_fan_only = false, std::set fan_modes = {}, + std::set swing_modes = {}) { this->minimum_temperature_ = minimum_temperature; this->maximum_temperature_ = maximum_temperature; this->temperature_step_ = temperature_step; @@ -58,8 +57,8 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: bool supports_heat_{true}; bool supports_dry_{false}; bool supports_fan_only_{false}; - std::vector fan_modes_ = {}; - std::vector swing_modes_ = {}; + std::set fan_modes_ = {}; + std::set swing_modes_ = {}; remote_transmitter::RemoteTransmitterComponent *transmitter_; sensor::Sensor *sensor_{nullptr}; diff --git a/esphome/components/daikin/daikin.h b/esphome/components/daikin/daikin.h index c0a472bce7..b4ac309de9 100644 --- a/esphome/components/daikin/daikin.h +++ b/esphome/components/daikin/daikin.h @@ -43,12 +43,11 @@ const uint8_t DAIKIN_STATE_FRAME_SIZE = 19; class DaikinClimate : public climate_ir::ClimateIR { public: DaikinClimate() - : climate_ir::ClimateIR( - DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, - climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} + : climate_ir::ClimateIR(DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, + climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} protected: // Transmit via IR the state of this climate controller. diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.h b/esphome/components/hitachi_ac344/hitachi_ac344.h index 9e850d9b53..1d0719f11b 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.h +++ b/esphome/components/hitachi_ac344/hitachi_ac344.h @@ -79,11 +79,10 @@ const uint16_t HITACHI_AC344_BITS = HITACHI_AC344_STATE_LENGTH * 8; class HitachiClimate : public climate_ir::ClimateIR { public: HitachiClimate() - : climate_ir::ClimateIR( - HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, - std::vector{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, - std::vector{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} + : climate_ir::ClimateIR(HITACHI_AC344_TEMP_MIN, HITACHI_AC344_TEMP_MAX, 1.0F, true, true, + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH}, + {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_HORIZONTAL}) {} protected: uint8_t remote_state_[HITACHI_AC344_STATE_LENGTH]{0x01, 0x10, 0x00, 0x40, 0x00, 0xFF, 0x00, 0xCC, 0x00, 0x00, 0x00, diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 481a6da54d..b3b196aad2 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -167,24 +167,38 @@ climate::ClimateTraits MideaAC::traits() { traits.set_visual_min_temperature(17); traits.set_visual_max_temperature(30); traits.set_visual_temperature_step(0.5); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(true); - traits.set_supports_dry_mode(true); - traits.set_supports_heat_mode(true); - traits.set_supports_fan_only_mode(true); - traits.set_supports_fan_mode_auto(true); - traits.set_supports_fan_mode_low(true); - traits.set_supports_fan_mode_medium(true); - traits.set_supports_fan_mode_high(true); + traits.set_supported_modes({ + climate::CLIMATE_MODE_OFF, + climate::CLIMATE_MODE_HEAT_COOL, + climate::CLIMATE_MODE_COOL, + climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_FAN_ONLY, + }); + traits.set_supported_fan_modes({ + climate::CLIMATE_FAN_AUTO, + climate::CLIMATE_FAN_LOW, + climate::CLIMATE_FAN_MEDIUM, + climate::CLIMATE_FAN_HIGH, + }); traits.set_supported_custom_fan_modes(this->traits_custom_fan_modes_); - traits.set_supports_swing_mode_off(true); - traits.set_supports_swing_mode_vertical(true); - traits.set_supports_swing_mode_horizontal(this->traits_swing_horizontal_); - traits.set_supports_swing_mode_both(this->traits_swing_both_); - traits.set_supports_preset_home(true); - traits.set_supports_preset_eco(this->traits_preset_eco_); - traits.set_supports_preset_sleep(this->traits_preset_sleep_); - traits.set_supports_preset_boost(this->traits_preset_boost_); + traits.set_supported_swing_modes({ + climate::CLIMATE_SWING_OFF, + climate::CLIMATE_SWING_VERTICAL, + }); + if (traits_swing_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (traits_swing_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + traits.set_supported_presets({ + climate::CLIMATE_PRESET_HOME, + }); + if (traits_preset_eco_) + traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); + if (traits_preset_sleep_) + traits.add_supported_preset(climate::CLIMATE_PRESET_SLEEP); + if (traits_preset_boost_) + traits.add_supported_preset(climate::CLIMATE_PRESET_BOOST); traits.set_supported_custom_presets(this->traits_custom_presets_); traits.set_supports_current_temperature(true); return traits; diff --git a/esphome/components/midea_ac/midea_climate.h b/esphome/components/midea_ac/midea_climate.h index 0a63312961..d5f29529df 100644 --- a/esphome/components/midea_ac/midea_climate.h +++ b/esphome/components/midea_ac/midea_climate.h @@ -1,9 +1,9 @@ #pragma once -#include "esphome/core/component.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/components/midea_dongle/midea_dongle.h" #include "esphome/components/climate/climate.h" +#include "esphome/components/midea_dongle/midea_dongle.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" #include "midea_frame.h" namespace esphome { @@ -26,10 +26,12 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu void set_preset_sleep(bool state) { this->traits_preset_sleep_ = state; } void set_preset_boost(bool state) { this->traits_preset_boost_ = state; } bool allow_preset(climate::ClimatePreset preset) const; - void set_custom_fan_modes(std::vector custom_fan_modes) { - this->traits_custom_fan_modes_ = custom_fan_modes; + void set_custom_fan_modes(std::set custom_fan_modes) { + this->traits_custom_fan_modes_ = std::move(custom_fan_modes); + } + void set_custom_presets(std::set custom_presets) { + this->traits_custom_presets_ = std::move(custom_presets); } - void set_custom_presets(std::vector custom_presets) { this->traits_custom_presets_ = custom_presets; } bool allow_custom_preset(const std::string &custom_preset) const; protected: @@ -53,8 +55,8 @@ class MideaAC : public midea_dongle::MideaAppliance, public climate::Climate, pu bool traits_preset_eco_{false}; bool traits_preset_sleep_{false}; bool traits_preset_boost_{false}; - std::vector traits_custom_fan_modes_{{}}; - std::vector traits_custom_presets_{{}}; + std::set traits_custom_fan_modes_{{}}; + std::set traits_custom_presets_{{}}; }; } // namespace midea_ac diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index 47923dc924..ed3193ae97 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -61,7 +61,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC // temp_step root["temp_step"] = traits.get_visual_temperature_step(); - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { // away_mode_command_topic root["away_mode_cmd_t"] = this->get_away_command_topic(); // away_mode_state_topic @@ -164,19 +164,19 @@ void MQTTClimateComponent::setup() { }); } - if (traits.get_supports_away()) { + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) { auto onoff = parse_on_off(payload.c_str()); auto call = this->device_->make_call(); switch (onoff) { case PARSE_ON: - call.set_away(true); + call.set_preset(CLIMATE_PRESET_AWAY); break; case PARSE_OFF: - call.set_away(false); + call.set_preset(CLIMATE_PRESET_HOME); break; case PARSE_TOGGLE: - call.set_away(!this->device_->away); + call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY); break; case PARSE_NONE: default: @@ -259,8 +259,8 @@ bool MQTTClimateComponent::publish_state_() { success = false; } - if (traits.get_supports_away()) { - std::string payload = ONOFF(this->device_->away); + if (traits.supports_preset(CLIMATE_PRESET_AWAY)) { + std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY); if (!this->publish(this->get_away_state_topic(), payload)) success = false; } diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index 0423ab27fc..b4660feb32 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -39,10 +39,14 @@ void PIDClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits PIDClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_heat_cool_mode(true); traits.set_supports_two_point_target_temperature(false); - traits.set_supports_cool_mode(this->supports_cool_()); - traits.set_supports_heat_mode(this->supports_heat_()); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_()) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_()) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_action(true); return traits; } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index a96c702473..65dd398197 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -50,12 +50,13 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { this->target_temperature_low = *call.get_target_temperature_low(); if (call.get_target_temperature_high().has_value()) this->target_temperature_high = *call.get_target_temperature_high(); - if (call.get_away().has_value()) { + if (call.get_preset().has_value()) { // setup_complete_ blocks modifying/resetting the temps immediately after boot if (this->setup_complete_) { - this->change_away_(*call.get_away()); + this->change_away_(*call.get_preset() == climate::CLIMATE_PRESET_AWAY); } else { - this->away = *call.get_away(); + this->preset = *call.get_preset(); + ; } } // set point validation @@ -78,27 +79,51 @@ void ThermostatClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits ThermostatClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(true); - traits.set_supports_auto_mode(this->supports_auto_); - traits.set_supports_heat_cool_mode(this->supports_heat_cool_); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_dry_mode(this->supports_dry_); - traits.set_supports_fan_only_mode(this->supports_fan_only_); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_fan_mode_on(this->supports_fan_mode_on_); - traits.set_supports_fan_mode_off(this->supports_fan_mode_off_); - traits.set_supports_fan_mode_auto(this->supports_fan_mode_auto_); - traits.set_supports_fan_mode_low(this->supports_fan_mode_low_); - traits.set_supports_fan_mode_medium(this->supports_fan_mode_medium_); - traits.set_supports_fan_mode_high(this->supports_fan_mode_high_); - traits.set_supports_fan_mode_middle(this->supports_fan_mode_middle_); - traits.set_supports_fan_mode_focus(this->supports_fan_mode_focus_); - traits.set_supports_fan_mode_diffuse(this->supports_fan_mode_diffuse_); - traits.set_supports_swing_mode_both(this->supports_swing_mode_both_); - traits.set_supports_swing_mode_horizontal(this->supports_swing_mode_horizontal_); - traits.set_supports_swing_mode_off(this->supports_swing_mode_off_); - traits.set_supports_swing_mode_vertical(this->supports_swing_mode_vertical_); + if (supports_auto_) + traits.add_supported_mode(climate::CLIMATE_MODE_AUTO); + if (supports_heat_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_dry_) + traits.add_supported_mode(climate::CLIMATE_MODE_DRY); + if (supports_fan_only_) + traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + + if (supports_fan_mode_on_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON); + if (supports_fan_mode_off_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF); + if (supports_fan_mode_auto_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO); + if (supports_fan_mode_low_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW); + if (supports_fan_mode_medium_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM); + if (supports_fan_mode_high_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH); + if (supports_fan_mode_middle_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE); + if (supports_fan_mode_focus_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS); + if (supports_fan_mode_diffuse_) + traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE); + + if (supports_swing_mode_both_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); + if (supports_swing_mode_horizontal_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL); + if (supports_swing_mode_off_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); + if (supports_swing_mode_vertical_) + traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); + + if (supports_away_) + traits.set_supported_presets({climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY}); + traits.set_supports_two_point_target_temperature(this->supports_two_points_); - traits.set_supports_away(this->supports_away_); traits.set_supports_action(true); return traits; } @@ -399,7 +424,7 @@ void ThermostatClimate::change_away_(bool away) { } else this->target_temperature = this->away_config_.default_temperature; } - this->away = away; + this->preset = away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME; } void ThermostatClimate::set_normal_config(const ThermostatClimateTargetTempConfig &normal_config) { this->normal_config_ = normal_config; diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index 9c9cf9f3e7..fbd25ee03a 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -68,8 +68,10 @@ void TuyaClimate::control(const climate::ClimateCall &call) { climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); - traits.set_supports_heat_mode(this->supports_heat_); - traits.set_supports_cool_mode(this->supports_cool_); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); traits.set_supports_action(true); return traits; } diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index e53e5fcccc..1505f4ead2 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -82,11 +82,14 @@ const uint32_t YASHIMA_CARRIER_FREQUENCY = 38000; climate::ClimateTraits YashimaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->sensor_ != nullptr); - traits.set_supports_heat_cool_mode(true); - traits.set_supports_cool_mode(this->supports_cool_); - traits.set_supports_heat_mode(this->supports_heat_); + + traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL}); + if (supports_cool_) + traits.add_supported_mode(climate::CLIMATE_MODE_COOL); + if (supports_heat_) + traits.add_supported_mode(climate::CLIMATE_MODE_HEAT); + traits.set_supports_two_point_target_temperature(false); - traits.set_supports_away(false); traits.set_visual_min_temperature(YASHIMA_TEMP_MIN); traits.set_visual_max_temperature(YASHIMA_TEMP_MAX); traits.set_visual_temperature_step(1); diff --git a/script/api_protobuf/api_protobuf.py b/script/api_protobuf/api_protobuf.py old mode 100644 new mode 100755 index 97cc95e556..05ad94a69d --- a/script/api_protobuf/api_protobuf.py +++ b/script/api_protobuf/api_protobuf.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """Python 3 script to automatically generate C++ classes for ESPHome's native API. It's pretty crappy spaghetti code, but it works. From e3f36c033ec486fe8e1182c62dc1f28adb8d6dcf Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 21 Jun 2021 22:02:18 +0200 Subject: [PATCH 036/105] API raise minor version for climate changes (#1947) --- esphome/components/api/api_connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 68187f259e..a04dc0630b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -615,7 +615,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { HelloResponse resp; resp.api_version_major = 1; - resp.api_version_minor = 4; + resp.api_version_minor = 5; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; this->connection_state_ = ConnectionState::CONNECTED; return resp; From 6009c7edb40a6ee2315687c056dd6170d3d877bc Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 22 Jun 2021 10:53:10 +0200 Subject: [PATCH 037/105] Disallow power_save_mode NONE if used together with BLE (#1950) --- esphome/components/http_request/__init__.py | 19 ++++------- esphome/components/wifi/__init__.py | 36 ++++++++++++++++++++- esphome/final_validate.py | 24 ++++++++++++++ tests/test1.yaml | 2 +- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index f4475060df..7dffdae27f 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -7,10 +7,7 @@ from esphome import automation from esphome.const import ( CONF_ID, CONF_TIMEOUT, - CONF_ESPHOME, CONF_METHOD, - CONF_ARDUINO_VERSION, - ARDUINO_VERSION_ESP8266, CONF_TRIGGER_ID, CONF_URL, ) @@ -78,23 +75,19 @@ CONFIG_SCHEMA = cv.Schema( ).extend(cv.COMPONENT_SCHEMA) -def validate_framework(config): - if CORE.is_esp32: +def validate_framework(value): + if not CORE.is_esp8266: + # only for ESP8266 return - # only for ESP8266 - path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] - version: str = fv.full_config.get().get_config_for_path(path) - - reverse_map = {v: k for k, v in ARDUINO_VERSION_ESP8266.items()} - framework_version = reverse_map.get(version) + framework_version = fv.get_arduino_framework_version() if framework_version is None or framework_version == "dev": return if framework_version < "2.5.1": raise cv.Invalid( - "This component is not supported on arduino framework version below 2.5.1", - path=[cv.ROOT_CONFIG_PATH] + path, + "This component is not supported on arduino framework version below 2.5.1, ", + "please check esphome->arduino_version", ) diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index fa28eaffd4..5a81d6a8f5 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -148,7 +148,41 @@ def final_validate(config): ) -FINAL_VALIDATE_SCHEMA = cv.Schema(final_validate) +def final_validate_power_esp32_ble(value): + if not CORE.is_esp32: + return + if value != "NONE": + # WiFi should be in modem sleep (!=NONE) with BLE coexistence + # https://docs.espressif.com/projects/esp-idf/en/v3.3.5/api-guides/wifi.html#station-sleep + return + framework_version = fv.get_arduino_framework_version() + if framework_version not in (None, "dev") and framework_version < "1.0.5": + # Only frameworks 1.0.5+ impacted + return + full = fv.full_config.get() + for conflicting in [ + "esp32_ble", + "esp32_ble_beacon", + "esp32_ble_server", + "esp32_ble_tracker", + ]: + if conflicting in full: + raise cv.Invalid( + f"power_save_mode NONE is incompatible with {conflicting}. " + f"Please remove the power save mode. See also " + f"https://github.com/esphome/issues/issues/2141#issuecomment-865688582" + ) + + +FINAL_VALIDATE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_POWER_SAVE_MODE): final_validate_power_esp32_ble, + }, + extra=cv.ALLOW_EXTRA, + ), + final_validate, +) def _validate(config): diff --git a/esphome/final_validate.py b/esphome/final_validate.py index 50fdbaf3f4..47071b5391 100644 --- a/esphome/final_validate.py +++ b/esphome/final_validate.py @@ -4,6 +4,13 @@ import contextvars from esphome.types import ConfigFragmentType, ID, ConfigPathType import esphome.config_validation as cv +from esphome.const import ( + ARDUINO_VERSION_ESP32, + ARDUINO_VERSION_ESP8266, + CONF_ESPHOME, + CONF_ARDUINO_VERSION, +) +from esphome.core import CORE class FinalValidateConfig(ABC): @@ -55,3 +62,20 @@ def id_declaration_match_schema(schema): return schema(declaration_config) return validator + + +def get_arduino_framework_version(): + path = [CONF_ESPHOME, CONF_ARDUINO_VERSION] + # This is run after core validation, so the property is set even if user didn't + version: str = full_config.get().get_config_for_path(path) + + if CORE.is_esp32: + version_map = ARDUINO_VERSION_ESP32 + elif CORE.is_esp8266: + version_map = ARDUINO_VERSION_ESP8266 + else: + raise ValueError("Platform not supported yet for this validator") + + reverse_map = {v: k for k, v in version_map.items()} + framework_version = reverse_map.get(version) + return framework_version diff --git a/tests/test1.yaml b/tests/test1.yaml index 29df5857d3..08e1d63534 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -80,7 +80,7 @@ wifi: dns2: 1.2.2.1 domain: .local reboot_timeout: 120s - power_save_mode: none + power_save_mode: light http_request: useragent: esphome/device From 1d8c170f48a59a2604a7c8c022603162a7f22fb6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:25:19 +0200 Subject: [PATCH 038/105] Add climate preset NONE again (#1951) --- esphome/components/api/api.proto | 15 ++++++----- esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 15 ++++++----- esphome/components/climate/climate_mode.cpp | 2 ++ esphome/components/climate/climate_mode.h | 30 +++++++++++---------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 87a7cf4749..a5bd9aec6d 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -710,13 +710,14 @@ enum ClimateAction { CLIMATE_ACTION_FAN = 6; } enum ClimatePreset { - CLIMATE_PRESET_HOME = 0; - CLIMATE_PRESET_AWAY = 1; - CLIMATE_PRESET_BOOST = 2; - CLIMATE_PRESET_COMFORT = 3; - CLIMATE_PRESET_ECO = 4; - CLIMATE_PRESET_SLEEP = 5; - CLIMATE_PRESET_ACTIVITY = 6; + CLIMATE_PRESET_NONE = 0; + CLIMATE_PRESET_HOME = 1; + CLIMATE_PRESET_AWAY = 2; + CLIMATE_PRESET_BOOST = 3; + CLIMATE_PRESET_COMFORT = 4; + CLIMATE_PRESET_ECO = 5; + CLIMATE_PRESET_SLEEP = 6; + CLIMATE_PRESET_ACTIVITY = 7; } message ListEntitiesClimateResponse { option (id) = 46; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 1e023f3988..e53ac2019a 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -192,6 +192,8 @@ template<> const char *proto_enum_to_string(enums::Climate } template<> const char *proto_enum_to_string(enums::ClimatePreset value) { switch (value) { + case enums::CLIMATE_PRESET_NONE: + return "CLIMATE_PRESET_NONE"; case enums::CLIMATE_PRESET_HOME: return "CLIMATE_PRESET_HOME"; case enums::CLIMATE_PRESET_AWAY: diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 7f37c0b94b..956cecdeb9 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -90,13 +90,14 @@ enum ClimateAction : uint32_t { CLIMATE_ACTION_FAN = 6, }; enum ClimatePreset : uint32_t { - CLIMATE_PRESET_HOME = 0, - CLIMATE_PRESET_AWAY = 1, - CLIMATE_PRESET_BOOST = 2, - CLIMATE_PRESET_COMFORT = 3, - CLIMATE_PRESET_ECO = 4, - CLIMATE_PRESET_SLEEP = 5, - CLIMATE_PRESET_ACTIVITY = 6, + CLIMATE_PRESET_NONE = 0, + CLIMATE_PRESET_HOME = 1, + CLIMATE_PRESET_AWAY = 2, + CLIMATE_PRESET_BOOST = 3, + CLIMATE_PRESET_COMFORT = 4, + CLIMATE_PRESET_ECO = 5, + CLIMATE_PRESET_SLEEP = 6, + CLIMATE_PRESET_ACTIVITY = 7, }; } // namespace enums diff --git a/esphome/components/climate/climate_mode.cpp b/esphome/components/climate/climate_mode.cpp index 099074a887..7a626942eb 100644 --- a/esphome/components/climate/climate_mode.cpp +++ b/esphome/components/climate/climate_mode.cpp @@ -84,6 +84,8 @@ const char *climate_swing_mode_to_string(ClimateSwingMode swing_mode) { const char *climate_preset_to_string(ClimatePreset preset) { switch (preset) { + case climate::CLIMATE_PRESET_NONE: + return "NONE"; case climate::CLIMATE_PRESET_HOME: return "HOME"; case climate::CLIMATE_PRESET_ECO: diff --git a/esphome/components/climate/climate_mode.h b/esphome/components/climate/climate_mode.h index 7afa2dae55..07fbf32b26 100644 --- a/esphome/components/climate/climate_mode.h +++ b/esphome/components/climate/climate_mode.h @@ -74,20 +74,22 @@ enum ClimateSwingMode : uint8_t { /// Enum for all modes a climate swing can be in enum ClimatePreset : uint8_t { - /// Preset is set to HOME - CLIMATE_PRESET_HOME = 0, - /// Preset is set to AWAY - CLIMATE_PRESET_AWAY = 1, - /// Preset is set to BOOST - CLIMATE_PRESET_BOOST = 2, - /// Preset is set to COMFORT - CLIMATE_PRESET_COMFORT = 3, - /// Preset is set to ECO - CLIMATE_PRESET_ECO = 4, - /// Preset is set to SLEEP - CLIMATE_PRESET_SLEEP = 5, - /// Preset is set to ACTIVITY - CLIMATE_PRESET_ACTIVITY = 6, + /// No preset is active + CLIMATE_PRESET_NONE = 0, + /// Device is in home preset + CLIMATE_PRESET_HOME = 1, + /// Device is in away preset + CLIMATE_PRESET_AWAY = 2, + /// Device is in boost preset + CLIMATE_PRESET_BOOST = 3, + /// Device is in comfort preset + CLIMATE_PRESET_COMFORT = 4, + /// Device is running an energy-saving preset + CLIMATE_PRESET_ECO = 5, + /// Device is prepared for sleep + CLIMATE_PRESET_SLEEP = 6, + /// Device is reacting to activity (e.g., movement sensors) + CLIMATE_PRESET_ACTIVITY = 7, }; /// Convert the given ClimateMode to a human-readable string. From 2bf70d7d003ebd857f2ca57d583c5b2ad86099af Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 23 Jun 2021 20:27:08 +0200 Subject: [PATCH 039/105] Compat argv parsing improvements (#1952) --- esphome/__main__.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/esphome/__main__.py b/esphome/__main__.py index 48f8bea083..232652db9f 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -514,14 +514,26 @@ def parse_args(argv): compat_parser.error = _raise - try: - result, unparsed = compat_parser.parse_known_args(argv[1:]) - last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) - argv = argv[0:last_option] + [result.command] + result.configuration + unparsed - deprecated_argv_suggestion = argv - except argparse.ArgumentError: - # This is not an old-style command line, so we don't have to do anything. - deprecated_argv_suggestion = None + deprecated_argv_suggestion = None + + if ["dashboard", "config"] == argv[1:3]: + # this is most likely meant in new-style arg format. do not try compat parsing + pass + else: + try: + result, unparsed = compat_parser.parse_known_args(argv[1:]) + last_option = len(argv) - len(unparsed) - 1 - len(result.configuration) + unparsed = [ + "--device" if arg in ("--upload-port", "--serial-port") else arg + for arg in unparsed + ] + argv = ( + argv[0:last_option] + [result.command] + result.configuration + unparsed + ) + deprecated_argv_suggestion = argv + except argparse.ArgumentError: + # This is not an old-style command line, so we don't have to do anything. + pass # And continue on with regular parsing parser = argparse.ArgumentParser( From 96721f305f7e8c0e514ec993d35b70869249b33a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:38:59 +1200 Subject: [PATCH 040/105] Bump dashboard to 20210623.0 (#1958) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 12500d57b7..cc9059f0d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210622.0 +esphome-dashboard==20210623.0 From 8600620305b12319a8b1c1ff74ef9faa60b087cc Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 24 Jun 2021 12:49:45 +1200 Subject: [PATCH 041/105] Bump version to v1.19.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index de33ebc2de..02d0491cfe 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 19 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 18c08f24adf5f2f54db8233ede436b9e1ef3c6b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 07:45:05 +1200 Subject: [PATCH 042/105] Bump version to v1.20.0b1 --- esphome/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/const.py b/esphome/const.py index 094dcace42..30c4748933 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,8 +1,8 @@ """Constants used by esphome.""" MAJOR_VERSION = 1 -MINOR_VERSION = 19 -PATCH_VERSION = "0b7" +MINOR_VERSION = 20 +PATCH_VERSION = "0b1" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 4dbf1c521ecb6a70dec86401f998686be83bcd52 Mon Sep 17 00:00:00 2001 From: SenexCrenshaw <35600301+SenexCrenshaw@users.noreply.github.com> Date: Wed, 14 Jul 2021 20:51:15 -0400 Subject: [PATCH 043/105] Nextion upload and sensors (#1464) Co-authored-by: Senex Crenshaw --- CODEOWNERS | 5 + esphome/components/nextion/__init__.py | 5 + esphome/components/nextion/automation.h | 30 + esphome/components/nextion/base_component.py | 126 ++ esphome/components/nextion/binary_sensor.py | 34 - .../nextion/binary_sensor/__init__.py | 54 + .../binary_sensor/nextion_binarysensor.cpp | 69 + .../binary_sensor/nextion_binarysensor.h | 42 + esphome/components/nextion/display.py | 75 +- esphome/components/nextion/nextion.cpp | 1143 ++++++++++++++--- esphome/components/nextion/nextion.h | 527 +++++++- esphome/components/nextion/nextion_base.h | 58 + .../components/nextion/nextion_commands.cpp | 234 ++++ .../components/nextion/nextion_component.cpp | 116 ++ .../components/nextion/nextion_component.h | 49 + .../nextion/nextion_component_base.h | 95 ++ esphome/components/nextion/nextion_upload.cpp | 343 +++++ esphome/components/nextion/sensor/__init__.py | 99 ++ .../nextion/sensor/nextion_sensor.cpp | 110 ++ .../nextion/sensor/nextion_sensor.h | 49 + esphome/components/nextion/switch/__init__.py | 39 + .../nextion/switch/nextion_switch.cpp | 52 + .../nextion/switch/nextion_switch.h | 34 + .../nextion/text_sensor/__init__.py | 38 + .../text_sensor/nextion_textsensor.cpp | 49 + .../nextion/text_sensor/nextion_textsensor.h | 32 + esphome/components/uart/uart.h | 1 + script/ci-custom.py | 3 +- tests/test1.yaml | 9 - tests/test3.yaml | 38 +- 30 files changed, 3295 insertions(+), 263 deletions(-) create mode 100644 esphome/components/nextion/automation.h create mode 100644 esphome/components/nextion/base_component.py delete mode 100644 esphome/components/nextion/binary_sensor.py create mode 100644 esphome/components/nextion/binary_sensor/__init__.py create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp create mode 100644 esphome/components/nextion/binary_sensor/nextion_binarysensor.h create mode 100644 esphome/components/nextion/nextion_base.h create mode 100644 esphome/components/nextion/nextion_commands.cpp create mode 100644 esphome/components/nextion/nextion_component.cpp create mode 100644 esphome/components/nextion/nextion_component.h create mode 100644 esphome/components/nextion/nextion_component_base.h create mode 100644 esphome/components/nextion/nextion_upload.cpp create mode 100644 esphome/components/nextion/sensor/__init__.py create mode 100644 esphome/components/nextion/sensor/nextion_sensor.cpp create mode 100644 esphome/components/nextion/sensor/nextion_sensor.h create mode 100644 esphome/components/nextion/switch/__init__.py create mode 100644 esphome/components/nextion/switch/nextion_switch.cpp create mode 100644 esphome/components/nextion/switch/nextion_switch.h create mode 100644 esphome/components/nextion/text_sensor/__init__.py create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.cpp create mode 100644 esphome/components/nextion/text_sensor/nextion_textsensor.h diff --git a/CODEOWNERS b/CODEOWNERS index d6769800cd..557fe7cc08 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -73,6 +73,11 @@ esphome/components/midea_ac/* @dudanov esphome/components/midea_dongle/* @dudanov esphome/components/mitsubishi/* @RubyBailey esphome/components/network/* @esphome/core +esphome/components/nextion/* @senexcrenshaw +esphome/components/nextion/binary_sensor/* @senexcrenshaw +esphome/components/nextion/sensor/* @senexcrenshaw +esphome/components/nextion/switch/* @senexcrenshaw +esphome/components/nextion/text_sensor/* @senexcrenshaw esphome/components/nfc/* @jesserockz esphome/components/number/* @esphome/core esphome/components/ota/* @esphome/core diff --git a/esphome/components/nextion/__init__.py b/esphome/components/nextion/__init__.py index 67a49df9fa..924d58198d 100644 --- a/esphome/components/nextion/__init__.py +++ b/esphome/components/nextion/__init__.py @@ -1,3 +1,8 @@ import esphome.codegen as cg +from esphome.components import uart nextion_ns = cg.esphome_ns.namespace("nextion") +Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) +nextion_ref = Nextion.operator("ref") + +CONF_NEXTION_ID = "nextion_id" diff --git a/esphome/components/nextion/automation.h b/esphome/components/nextion/automation.h new file mode 100644 index 0000000000..5f4219acb1 --- /dev/null +++ b/esphome/components/nextion/automation.h @@ -0,0 +1,30 @@ +#pragma once +#include "esphome/core/automation.h" +#include "nextion.h" + +namespace esphome { +namespace nextion { + +class SetupTrigger : public Trigger<> { + public: + explicit SetupTrigger(Nextion *nextion) { + nextion->add_setup_state_callback([this]() { this->trigger(); }); + } +}; + +class SleepTrigger : public Trigger<> { + public: + explicit SleepTrigger(Nextion *nextion) { + nextion->add_sleep_state_callback([this]() { this->trigger(); }); + } +}; + +class WakeTrigger : public Trigger<> { + public: + explicit WakeTrigger(Nextion *nextion) { + nextion->add_wake_state_callback([this]() { this->trigger(); }); + } +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/base_component.py b/esphome/components/nextion/base_component.py new file mode 100644 index 0000000000..3bf828aafa --- /dev/null +++ b/esphome/components/nextion/base_component.py @@ -0,0 +1,126 @@ +from string import ascii_letters, digits +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.components import color + +from . import CONF_NEXTION_ID +from . import Nextion + +CONF_VARIABLE_NAME = "variable_name" +CONF_COMPONENT_NAME = "component_name" +CONF_WAVE_CHANNEL_ID = "wave_channel_id" +CONF_WAVE_MAX_VALUE = "wave_max_value" +CONF_PRECISION = "precision" +CONF_WAVEFORM_SEND_LAST_VALUE = "waveform_send_last_value" +CONF_TFT_URL = "tft_url" +CONF_ON_SLEEP = "on_sleep" +CONF_ON_WAKE = "on_wake" +CONF_ON_SETUP = "on_setup" +CONF_TOUCH_SLEEP_TIMEOUT = "touch_sleep_timeout" +CONF_WAKE_UP_PAGE = "wake_up_page" +CONF_AUTO_WAKE_ON_TOUCH = "auto_wake_on_touch" +CONF_WAVE_MAX_LENGTH = "wave_max_length" +CONF_BACKGROUND_COLOR = "background_color" +CONF_BACKGROUND_PRESSED_COLOR = "background_pressed_color" +CONF_FOREGROUND_COLOR = "foreground_color" +CONF_FOREGROUND_PRESSED_COLOR = "foreground_pressed_color" +CONF_FONT_ID = "font_id" +CONF_VISIBLE = "visible" + + +def NextionName(value): + valid_chars = ascii_letters + digits + "." + if not isinstance(value, str) or len(value) > 29: + raise cv.Invalid("Must be a string less than 29 characters") + + for char in value: + if char not in valid_chars: + raise cv.Invalid( + "Must only consist of upper/lowercase characters, numbers and the period '.'. The character '{}' cannot be used.".format( + char + ) + ) + + return value + + +CONFIG_BASE_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), + cv.Optional(CONF_BACKGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color), + cv.Optional(CONF_VISIBLE, default=True): cv.boolean, + } +) + + +CONFIG_TEXT_COMPONENT_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Required(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + +CONFIG_BINARY_SENSOR_SCHEMA = CONFIG_BASE_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_COMPONENT_NAME): NextionName, + cv.Optional(CONF_VARIABLE_NAME): NextionName, + } + ) +) + +CONFIG_SENSOR_COMPONENT_SCHEMA = CONFIG_BINARY_SENSOR_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FONT_ID): cv.int_range(min=0, max=255), + } + ) +) + + +CONFIG_SWITCH_COMPONENT_SCHEMA = CONFIG_SENSOR_COMPONENT_SCHEMA.extend( + cv.Schema( + { + cv.Optional(CONF_FOREGROUND_PRESSED_COLOR): cv.use_id(color), + cv.Optional(CONF_BACKGROUND_PRESSED_COLOR): cv.use_id(color), + } + ) +) + + +async def setup_component_core_(var, config, arg): + + if CONF_VARIABLE_NAME in config: + cg.add(var.set_variable_name(config[CONF_VARIABLE_NAME])) + elif CONF_COMPONENT_NAME in config: + cg.add( + var.set_variable_name( + config[CONF_COMPONENT_NAME], + config[CONF_COMPONENT_NAME] + arg, + ) + ) + + if CONF_BACKGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_COLOR]) + cg.add(var.set_background_color(color_component)) + + if CONF_BACKGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_BACKGROUND_PRESSED_COLOR]) + cg.add(var.set_background_pressed_color(color_component)) + + if CONF_FOREGROUND_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_COLOR]) + cg.add(var.set_foreground_color(color_component)) + + if CONF_FOREGROUND_PRESSED_COLOR in config: + color_component = await cg.get_variable(config[CONF_FOREGROUND_PRESSED_COLOR]) + cg.add(var.set_foreground_pressed_color(color_component)) + + if CONF_FONT_ID in config: + cg.add(var.set_font_id(config[CONF_FONT_ID])) + + if CONF_VISIBLE in config: + cg.add(var.set_visible(config[CONF_VISIBLE])) diff --git a/esphome/components/nextion/binary_sensor.py b/esphome/components/nextion/binary_sensor.py deleted file mode 100644 index ed4e8d832a..0000000000 --- a/esphome/components/nextion/binary_sensor.py +++ /dev/null @@ -1,34 +0,0 @@ -import esphome.codegen as cg -import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID -from . import nextion_ns -from .display import Nextion - -DEPENDENCIES = ["display"] - -CONF_NEXTION_ID = "nextion_id" - -NextionTouchComponent = nextion_ns.class_( - "NextionTouchComponent", binary_sensor.BinarySensor -) - -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionTouchComponent), - cv.GenerateID(CONF_NEXTION_ID): cv.use_id(Nextion), - cv.Required(CONF_PAGE_ID): cv.uint8_t, - cv.Required(CONF_COMPONENT_ID): cv.uint8_t, - } -) - - -async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) - - hub = await cg.get_variable(config[CONF_NEXTION_ID]) - cg.add(hub.register_touch_component(var)) - - cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) - cg.add(var.set_page_id(config[CONF_PAGE_ID])) diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py new file mode 100644 index 0000000000..090fae3429 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -0,0 +1,54 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor + +from esphome.const import CONF_COMPONENT_ID, CONF_PAGE_ID, CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + + +from ..base_component import ( + setup_component_core_, + CONFIG_BINARY_SENSOR_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionBinarySensor = nextion_ns.class_( + "NextionBinarySensor", binary_sensor.BinarySensor, cg.PollingComponent +) + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionBinarySensor), + cv.Optional(CONF_PAGE_ID): cv.uint8_t, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + } + ) + .extend(CONFIG_BINARY_SENSOR_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_at_least_one_key( + CONF_PAGE_ID, + CONF_COMPONENT_ID, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + ), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await binary_sensor.register_binary_sensor(var, config) + await cg.register_component(var, config) + + if config.keys() >= {CONF_PAGE_ID, CONF_COMPONENT_ID}: + cg.add(hub.register_touch_component(var)) + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + cg.add(var.set_page_id(config[CONF_PAGE_ID])) + + if CONF_COMPONENT_NAME in config or CONF_VARIABLE_NAME in config: + await setup_component_core_(var, config, ".val") + cg.add(hub.register_binarysensor_component(var)) diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp new file mode 100644 index 0000000000..bf6e74cb38 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.cpp @@ -0,0 +1,69 @@ +#include "nextion_binarysensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_binarysensor"; + +void NextionBinarySensor::process_bool(const std::string &variable_name, bool state) { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + if (this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed binarysensor \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionBinarySensor::process_touch(uint8_t page_id, uint8_t component_id, bool state) { + if (this->page_id_ == page_id && this->component_id_ == component_id) { + this->publish_state(state); + } +} + +void NextionBinarySensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->variable_name_.empty()) // This is a touch component + return; + + this->nextion_->add_to_get_queue(this); +} + +void NextionBinarySensor::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (this->component_id_ == 0) // This is a legacy touch component + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %s", this->variable_name_.c_str(), + ONOFF(this->variable_name_.c_str())); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/binary_sensor/nextion_binarysensor.h b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h new file mode 100644 index 0000000000..b6b23ada85 --- /dev/null +++ b/esphome/components/nextion/binary_sensor/nextion_binarysensor.h @@ -0,0 +1,42 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionBinarySensor; + +class NextionBinarySensor : public NextionComponent, + public binary_sensor::BinarySensorInitiallyOff, + public PollingComponent { + public: + NextionBinarySensor(NextionBase *nextion) { this->nextion_ = nextion; } + + void update_component() override { this->update(); } + void update() override; + void send_state_to_nextion() override { this->set_state(this->state, false); }; + void process_bool(const std::string &variable_name, bool state) override; + void process_touch(uint8_t page_id, uint8_t component_id, bool state) override; + + // Set the components page id for Nextion Touch Component + void set_page_id(uint8_t page_id) { page_id_ = page_id; } + // Set the components component id for Nextion Touch Component + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + NextionQueueType get_queue_type() override { return NextionQueueType::BINARY_SENSOR; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + uint8_t page_id_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 7d7018a4c4..e693b2f1ec 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -1,20 +1,58 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation from esphome.components import display, uart -from esphome.const import CONF_ID, CONF_LAMBDA, CONF_BRIGHTNESS -from . import nextion_ns +from esphome.const import ( + CONF_ID, + CONF_LAMBDA, + CONF_BRIGHTNESS, + CONF_TRIGGER_ID, +) + +from . import Nextion, nextion_ns, nextion_ref +from .base_component import ( + CONF_ON_SLEEP, + CONF_ON_WAKE, + CONF_ON_SETUP, + CONF_TFT_URL, + CONF_TOUCH_SLEEP_TIMEOUT, + CONF_WAKE_UP_PAGE, + CONF_AUTO_WAKE_ON_TOUCH, +) + +CODEOWNERS = ["@senexcrenshaw"] DEPENDENCIES = ["uart"] -AUTO_LOAD = ["binary_sensor"] +AUTO_LOAD = ["binary_sensor", "switch", "sensor", "text_sensor"] -Nextion = nextion_ns.class_("Nextion", cg.PollingComponent, uart.UARTDevice) -NextionRef = Nextion.operator("ref") +SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template()) +SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template()) +WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template()) CONFIG_SCHEMA = ( display.BASIC_DISPLAY_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(Nextion), + cv.Optional(CONF_TFT_URL): cv.string, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, + cv.Optional(CONF_ON_SETUP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger), + } + ), + cv.Optional(CONF_ON_SLEEP): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger), + } + ), + cv.Optional(CONF_ON_WAKE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger), + } + ), + cv.Optional(CONF_TOUCH_SLEEP_TIMEOUT): cv.int_range(min=3, max=65535), + cv.Optional(CONF_WAKE_UP_PAGE): cv.positive_int, + cv.Optional(CONF_AUTO_WAKE_ON_TOUCH, default=True): cv.boolean, } ) .extend(cv.polling_component_schema("5s")) @@ -31,8 +69,33 @@ async def to_code(config): cg.add(var.set_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(NextionRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(nextion_ref, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) + if CONF_TFT_URL in config: + cg.add_define("USE_TFT_UPLOAD") + cg.add(var.set_tft_url(config[CONF_TFT_URL])) + + if CONF_TOUCH_SLEEP_TIMEOUT in config: + cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT])) + + if CONF_WAKE_UP_PAGE in config: + cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE])) + + if CONF_AUTO_WAKE_ON_TOUCH in config: + cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH])) + await display.register_display(var, config) + + for conf in config.get(CONF_ON_SETUP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_SLEEP, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + for conf in config.get(CONF_ON_WAKE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fe0767342b..9a5424917f 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -1,5 +1,7 @@ #include "nextion.h" +#include "esphome/core/util.h" #include "esphome/core/log.h" +#include "esphome/core/application.h" namespace esphome { namespace nextion { @@ -7,69 +9,171 @@ namespace nextion { static const char *const TAG = "nextion"; void Nextion::setup() { - this->send_command_no_ack(""); - this->send_command_printf("bkcmd=3"); - this->set_backlight_brightness(static_cast(brightness_ * 100)); - this->goto_page("0"); + this->is_setup_ = false; + this->ignore_is_setup_ = true; + + // Wake up the nextion + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + this->send_command_("bkcmd=0"); + this->send_command_("sleep=0"); + + // Reboot it + this->send_command_("rest"); + + this->ignore_is_setup_ = false; } -float Nextion::get_setup_priority() const { return setup_priority::PROCESSOR; } + +bool Nextion::send_command_(const std::string &command) { + if (!this->ignore_is_setup_ && !this->is_setup()) { + return false; + } + + ESP_LOGN(TAG, "send_command %s", command.c_str()); + + this->write_str(command.c_str()); + const uint8_t to_send[3] = {0xFF, 0xFF, 0xFF}; + this->write_array(to_send, sizeof(to_send)); + return true; +} + +bool Nextion::check_connect_() { + if (this->get_is_connected_()) + return true; + + if (this->comok_sent_ == 0) { + this->reset_(false); + + this->ignore_is_setup_ = true; + this->send_command_("boguscommand=0"); // bogus command. needed sometimes after updating + this->send_command_("connect"); + + this->comok_sent_ = millis(); + this->ignore_is_setup_ = false; + + return false; + } + + if (millis() - this->comok_sent_ <= 500) // Wait 500 ms + return false; + + std::string response; + + this->recv_ret_string_(response, 0, false); + if (response.empty() || response.find("comok") == std::string::npos) { +#ifdef NEXTION_PROTOCOL_LOG + ESP_LOGN(TAG, "Bad connect request %s", response.c_str()); + for (int i = 0; i < response.length(); i++) { + ESP_LOGN(TAG, "response %s %d %d %c", response.c_str(), i, response[i], response[i]); + } +#endif + + ESP_LOGW(TAG, "Nextion is not connected! "); + comok_sent_ = 0; + return false; + } + + this->ignore_is_setup_ = true; + ESP_LOGI(TAG, "Nextion is connected"); + this->is_connected_ = true; + + ESP_LOGN(TAG, "connect request %s", response.c_str()); + + size_t start; + size_t end = 0; + std::vector connect_info; + while ((start = response.find_first_not_of(',', end)) != std::string::npos) { + end = response.find(',', start); + connect_info.push_back(response.substr(start, end - start)); + } + + if (connect_info.size() == 7) { + ESP_LOGN(TAG, "Received connect_info %zu", connect_info.size()); + + this->device_model_ = connect_info[2]; + this->firmware_version_ = connect_info[3]; + this->serial_number_ = connect_info[5]; + this->flash_size_ = connect_info[6]; + } else { + ESP_LOGE(TAG, "Nextion returned bad connect value \"%s\"", response.c_str()); + } + + this->ignore_is_setup_ = false; + this->dump_config(); + return true; +} + +void Nextion::reset_(bool reset_nextion) { + uint8_t d; + + while (this->available()) { // Clear receive buffer + this->read_byte(&d); + }; + this->nextion_queue_.clear(); +} + +void Nextion::dump_config() { + ESP_LOGCONFIG(TAG, "Nextion:"); + ESP_LOGCONFIG(TAG, " Device Model: %s", this->device_model_.c_str()); + ESP_LOGCONFIG(TAG, " Firmware Version: %s", this->firmware_version_.c_str()); + ESP_LOGCONFIG(TAG, " Serial Number: %s", this->serial_number_.c_str()); + ESP_LOGCONFIG(TAG, " Flash Size: %s", this->flash_size_.c_str()); + ESP_LOGCONFIG(TAG, " Wake On Touch: %s", this->auto_wake_on_touch_ ? "True" : "False"); + + if (this->touch_sleep_timeout_ != 0) { + ESP_LOGCONFIG(TAG, " Touch Timeout: %d", this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + ESP_LOGCONFIG(TAG, " Wake Up Page : %d", this->wake_up_page_); + } +} + +float Nextion::get_setup_priority() const { return setup_priority::DATA; } void Nextion::update() { + if (!this->is_setup()) { + return; + } if (this->writer_.has_value()) { (*this->writer_)(*this); } } -void Nextion::send_command_no_ack(const char *command) { - // Flush RX... - this->loop(); - this->write_str(command); - const uint8_t data[3] = {0xFF, 0xFF, 0xFF}; - this->write_array(data, sizeof(data)); +void Nextion::add_sleep_state_callback(std::function &&callback) { + this->sleep_callback_.add(std::move(callback)); } -bool Nextion::ack_() { - if (!this->wait_for_ack_) - return true; +void Nextion::add_wake_state_callback(std::function &&callback) { + this->wake_callback_.add(std::move(callback)); +} - uint32_t start = millis(); - while (!this->read_until_ack_()) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Waiting for ACK timed out!"); - return false; - } +void Nextion::add_setup_state_callback(std::function &&callback) { + this->setup_callback_.add(std::move(callback)); +} + +void Nextion::update_all_components() { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->update_component(); + } + for (auto *sensortype : this->sensortype_) { + sensortype->update_component(); + } + for (auto *switchtype : this->switchtype_) { + switchtype->update_component(); + } + for (auto *textsensortype : this->textsensortype_) { + textsensortype->update_component(); } - return true; } -void Nextion::set_component_text(const char *component, const char *text) { - this->send_command_printf("%s.txt=\"%s\"", component, text); -} -void Nextion::set_component_value(const char *component, int value) { - this->send_command_printf("%s.val=%d", component, value); -} -void Nextion::display_picture(int picture_id, int x_start, int y_start) { - this->send_command_printf("pic %d %d %d", x_start, y_start, picture_id); -} -void Nextion::set_component_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_background_color(const char *component, const char *color) { - this->send_command_printf("%s.bco2=\"%s\"", component, color); -} -void Nextion::set_component_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco=\"%s\"", component, color); -} -void Nextion::set_component_pressed_font_color(const char *component, const char *color) { - this->send_command_printf("%s.pco2=\"%s\"", component, color); -} -void Nextion::set_component_coordinates(const char *component, int x, int y) { - this->send_command_printf("%s.xcen=%d", component, x); - this->send_command_printf("%s.ycen=%d", component, y); -} -void Nextion::set_component_font(const char *component, uint8_t font_id) { - this->send_command_printf("%s.font=%d", component, font_id); -} -void Nextion::goto_page(const char *page) { this->send_command_printf("page %s", page); } + bool Nextion::send_command_printf(const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + char buffer[256]; va_list arg; va_start(arg, format); @@ -79,208 +183,911 @@ bool Nextion::send_command_printf(const char *format, ...) { ESP_LOGW(TAG, "Building command for format '%s' failed!", format); return false; } - this->send_command_no_ack(buffer); - if (!this->ack_()) { - ESP_LOGW(TAG, "Sending command '%s' failed because no ACK was received", buffer); + + if (this->send_command_(buffer)) { + this->add_no_result_to_queue_("send_command_printf"); + return true; + } + return false; +} + +#ifdef NEXTION_PROTOCOL_LOG +void Nextion::print_queue_members_() { + ESP_LOGN(TAG, "print_queue_members_ (top 10) size %zu", this->nextion_queue_.size()); + ESP_LOGN(TAG, "*******************************************"); + int count = 0; + for (auto *i : this->nextion_queue_) { + if (count++ == 10) + break; + + if (i == nullptr) { + ESP_LOGN(TAG, "Nextion queue is null"); + } else { + ESP_LOGN(TAG, "Nextion queue type: %d:%s , name: %s", i->component->get_queue_type(), + i->component->get_queue_type_string().c_str(), i->component->get_variable_name().c_str()); + } + } + ESP_LOGN(TAG, "*******************************************"); +} +#endif + +void Nextion::loop() { + if (!this->check_connect_() || this->is_updating_) + return; + + if (this->nextion_reports_is_setup_ && !this->sent_setup_commands_) { + this->ignore_is_setup_ = true; + this->sent_setup_commands_ = true; + this->send_command_("bkcmd=3"); // Always, returns 0x00 to 0x23 result of serial command. + + this->set_backlight_brightness(this->brightness_); + this->goto_page("0"); + + this->set_auto_wake_on_touch(this->auto_wake_on_touch_); + + if (this->touch_sleep_timeout_ != 0) { + this->set_touch_sleep_timeout(this->touch_sleep_timeout_); + } + + if (this->wake_up_page_ != -1) { + this->set_wake_up_page(this->wake_up_page_); + } + + this->ignore_is_setup_ = false; + } + + this->process_serial_(); // Receive serial data + this->process_nextion_commands_(); // Process nextion return commands + + if (!this->nextion_reports_is_setup_) { + if (this->started_ms_ == 0) + this->started_ms_ = millis(); + + if (this->started_ms_ + this->startup_override_ms_ < millis()) { + ESP_LOGD(TAG, "Manually set nextion report ready"); + this->nextion_reports_is_setup_ = true; + } + } +} + +bool Nextion::remove_from_q_(bool report_empty) { + if (this->nextion_queue_.empty()) { + if (report_empty) + ESP_LOGE(TAG, "Nextion queue is empty!"); return false; } + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + ESP_LOGN(TAG, "Removing %s from the queue", component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + delete nb; + this->nextion_queue_.pop_front(); return true; } -void Nextion::hide_component(const char *component) { this->send_command_printf("vis %s,0", component); } -void Nextion::show_component(const char *component) { this->send_command_printf("vis %s,1", component); } -void Nextion::enable_component_touch(const char *component) { this->send_command_printf("tsw %s,1", component); } -void Nextion::disable_component_touch(const char *component) { this->send_command_printf("tsw %s,0", component); } -void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { - this->send_command_printf("add %d,%u,%u", component_id, channel_number, value); -} -void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("fill %d,%d,%d,%d,%s", x1, y1, width, height, color); -} -void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { - this->send_command_printf("line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); -} -void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { - this->send_command_printf("draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); -} -void Nextion::circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cir %d,%d,%d,%s", center_x, center_y, radius, color); -} -void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { - this->send_command_printf("cirs %d,%d,%d,%s", center_x, center_y, radius, color); -} -bool Nextion::read_until_ack_() { - while (this->available() >= 4) { - // flush preceding filler bytes - uint8_t temp; - while (this->available() && this->peek_byte(&temp) && temp == 0xFF) - this->read_byte(&temp); - if (!this->available()) - break; +void Nextion::process_serial_() { + uint8_t d; - uint8_t event; - // event type - this->read_byte(&event); + while (this->available()) { + read_byte(&d); + this->command_data_ += d; + } +} +// nextion.tech/instruction-set/ +void Nextion::process_nextion_commands_() { + if (this->command_data_.length() == 0) { + return; + } - uint8_t data[255]; - // total length of data (including end bytes) - uint8_t data_length = 0; - // message is terminated by three consecutive 0xFF - // this variable keeps track of ohow many of those have - // been received - uint8_t end_length = 0; - while (this->available() && end_length < 3 && data_length < sizeof(data)) { - uint8_t byte; - this->read_byte(&byte); - if (byte == 0xFF) { - end_length++; - } else { - end_length = 0; - } - data[data_length++] = byte; + size_t to_process_length = 0; + std::string to_process; + + ESP_LOGN(TAG, "this->command_data_ %s length %d", this->command_data_.c_str(), this->command_data_.length()); +#ifdef NEXTION_PROTOCOL_LOG + this->print_queue_members_(); +#endif + while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) { + ESP_LOGN(TAG, "print_queue_members_ size %zu", this->nextion_queue_.size()); + while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() && + static_cast(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) { + ++to_process_length; + ESP_LOGN(TAG, "Add extra 0xFF to process"); } - if (end_length != 3) { - ESP_LOGW(TAG, "Received unknown filler end bytes from Nextion!"); - continue; - } + this->nextion_event_ = this->command_data_[0]; - data_length -= 3; // remove filler bytes + to_process_length -= 1; + to_process = this->command_data_.substr(1, to_process_length); - bool invalid_data_length = false; - switch (event) { - case 0x01: // successful execution of instruction (ACK) - return true; - case 0x00: // invalid instruction + switch (this->nextion_event_) { + case 0x00: // instruction sent by user has failed ESP_LOGW(TAG, "Nextion reported invalid instruction!"); + this->remove_from_q_(); + break; - case 0x02: // component ID invalid - ESP_LOGW(TAG, "Nextion reported component ID invalid!"); + case 0x01: // instruction sent by user was successful + + ESP_LOGVV(TAG, "instruction sent by user was successful"); + ESP_LOGN(TAG, "this->nextion_queue_.empty() %s", this->nextion_queue_.empty() ? "True" : "False"); + + this->remove_from_q_(); + if (!this->is_setup_) { + if (this->nextion_queue_.empty()) { + ESP_LOGD(TAG, "Nextion is setup"); + this->is_setup_ = true; + this->setup_callback_.call(); + } + } + break; - case 0x03: // page ID invalid + case 0x02: // invalid Component ID or name was used + this->remove_from_q_(); + break; + case 0x03: // invalid Page ID or name was used ESP_LOGW(TAG, "Nextion reported page ID invalid!"); + this->remove_from_q_(); break; - case 0x04: // picture ID invalid + case 0x04: // invalid Picture ID was used ESP_LOGW(TAG, "Nextion reported picture ID invalid!"); + this->remove_from_q_(); break; - case 0x05: // font ID invalid + case 0x05: // invalid Font ID was used ESP_LOGW(TAG, "Nextion reported font ID invalid!"); + this->remove_from_q_(); break; - case 0x11: // baud rate setting invalid + case 0x06: // File operation fails + ESP_LOGW(TAG, "Nextion File operation fail!"); + break; + case 0x09: // Instructions with CRC validation fails their CRC check + ESP_LOGW(TAG, "Nextion Instructions with CRC validation fails their CRC check!"); + break; + case 0x11: // invalid Baud rate was used ESP_LOGW(TAG, "Nextion reported baud rate invalid!"); break; - case 0x12: // curve control ID number or channel number is invalid - ESP_LOGW(TAG, "Nextion reported control/channel ID invalid!"); + case 0x12: // invalid Waveform ID or Channel # was used + + if (!this->nextion_queue_.empty()) { + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + ESP_LOGW(TAG, "Nextion reported invalid Waveform ID %d or Channel # %d was used!", + component->get_component_id(), component->get_wave_channel_id()); + + ESP_LOGN(TAG, "Removing waveform from queue with component id %d and waveform id %d", + component->get_component_id(), component->get_wave_channel_id()); + + found = index; + + delete component; + delete nb; + + break; + } + ++index; + } + + if (found != -1) { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } else { + ESP_LOGW( + TAG, + "Nextion reported invalid Waveform ID or Channel # was used but no waveform sensor in queue found!"); + } + } break; case 0x1A: // variable name invalid - ESP_LOGW(TAG, "Nextion reported variable name invalid!"); + this->remove_from_q_(); + break; case 0x1B: // variable operation invalid ESP_LOGW(TAG, "Nextion reported variable operation invalid!"); + this->remove_from_q_(); break; case 0x1C: // failed to assign ESP_LOGW(TAG, "Nextion reported failed to assign variable!"); + this->remove_from_q_(); break; case 0x1D: // operate EEPROM failed ESP_LOGW(TAG, "Nextion reported operating EEPROM failed!"); break; case 0x1E: // parameter quantity invalid ESP_LOGW(TAG, "Nextion reported parameter quantity invalid!"); + this->remove_from_q_(); break; case 0x1F: // IO operation failed ESP_LOGW(TAG, "Nextion reported component I/O operation invalid!"); break; case 0x20: // undefined escape characters ESP_LOGW(TAG, "Nextion reported undefined escape characters!"); + this->remove_from_q_(); break; case 0x23: // too long variable name ESP_LOGW(TAG, "Nextion reported too long variable name!"); + this->remove_from_q_(); + + break; + case 0x24: // Serial Buffer overflow occurs + ESP_LOGW(TAG, "Nextion reported Serial Buffer overflow!"); break; case 0x65: { // touch event return data - if (data_length != 3) { - invalid_data_length = true; + if (to_process_length != 3) { + ESP_LOGW(TAG, "Touch event data is expecting 3, received %zu", to_process_length); + break; } - uint8_t page_id = data[0]; - uint8_t component_id = data[1]; - uint8_t touch_event = data[2]; // 0 -> release, 1 -> press + uint8_t page_id = to_process[0]; + uint8_t component_id = to_process[1]; + uint8_t touch_event = to_process[2]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch page=%u component=%u type=%s", page_id, component_id, touch_event ? "PRESS" : "RELEASE"); for (auto *touch : this->touch_) { - touch->process(page_id, component_id, touch_event); + touch->process_touch(page_id, component_id, touch_event != 0); } break; } - case 0x67: - case 0x68: { // touch coordinate data - if (data_length != 5) { - invalid_data_length = true; + case 0x67: { // Touch Coordinate (awake) + break; + } + case 0x68: { // touch coordinate data (sleep) + + if (to_process_length != 5) { + ESP_LOGW(TAG, "Touch coordinate data is expecting 5, received %zu", to_process_length); + ESP_LOGW(TAG, "%s", to_process.c_str()); break; } - uint16_t x = (uint16_t(data[0]) << 8) | data[1]; - uint16_t y = (uint16_t(data[2]) << 8) | data[3]; - uint8_t touch_event = data[4]; // 0 -> release, 1 -> press + + uint16_t x = (uint16_t(to_process[0]) << 8) | to_process[1]; + uint16_t y = (uint16_t(to_process[2]) << 8) | to_process[3]; + uint8_t touch_event = to_process[4]; // 0 -> release, 1 -> press ESP_LOGD(TAG, "Got touch at x=%u y=%u type=%s", x, y, touch_event ? "PRESS" : "RELEASE"); break; } - case 0x66: // sendme page id + case 0x66: { + break; + } // sendme page id + + // 0x70 0x61 0x62 0x31 0x32 0x33 0xFF 0xFF 0xFF + // Returned when using get command for a string. + // Each byte is converted to char. + // data: ab123 case 0x70: // string variable data return + { + if (this->nextion_queue_.empty()) { + ESP_LOGW(TAG, "ERROR: Received string return but the queue is empty"); + break; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::TEXT_SENSOR) { + ESP_LOGE(TAG, "ERROR: Received string return but next in queue \"%s\" is not a text sensor", + component->get_variable_name().c_str()); + } else { + ESP_LOGN(TAG, "Received get_string response: \"%s\" for component id: %s, type: %s", to_process.c_str(), + component->get_variable_name().c_str(), component->get_queue_type_string().c_str()); + component->set_state_from_string(to_process, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + // 0x71 0x01 0x02 0x03 0x04 0xFF 0xFF 0xFF + // Returned when get command to return a number + // 4 byte 32-bit value in little endian order. + // (0x01+0x02*256+0x03*65536+0x04*16777216) + // data: 67305985 case 0x71: // numeric variable data return - case 0x86: // device automatically enters into sleep mode + { + if (this->nextion_queue_.empty()) { + ESP_LOGE(TAG, "ERROR: Received numeric return but the queue is empty"); + break; + } + + if (to_process_length == 0) { + ESP_LOGE(TAG, "ERROR: Received numeric return but no data!"); + break; + } + + int dataindex = 0; + + int value = 0; + + for (int i = 0; i < 4; ++i) { + value += to_process[i] << (8 * i); + ++dataindex; + } + + NextionQueue *nb = this->nextion_queue_.front(); + NextionComponentBase *component = nb->component; + + if (component->get_queue_type() != NextionQueueType::SENSOR && + component->get_queue_type() != NextionQueueType::BINARY_SENSOR && + component->get_queue_type() != NextionQueueType::SWITCH) { + ESP_LOGE(TAG, "ERROR: Received numeric return but next in queue \"%s\" is not a valid sensor type %d", + component->get_variable_name().c_str(), component->get_queue_type()); + } else { + ESP_LOGN(TAG, "Received numeric return for variable %s, queue type %d:%s, value %d", + component->get_variable_name().c_str(), component->get_queue_type(), + component->get_queue_type_string().c_str(), value); + component->set_state_from_int(value, true, false); + } + + delete nb; + this->nextion_queue_.pop_front(); + + break; + } + + case 0x86: { // device automatically enters into sleep mode + ESP_LOGVV(TAG, "Received Nextion entering sleep automatically"); + this->is_sleeping_ = true; + this->sleep_callback_.call(); + break; + } case 0x87: // device automatically wakes up + { + ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); + this->is_sleeping_ = false; + this->wake_callback_.call(); + break; + } case 0x88: // system successful start up - case 0x89: // start SD card upgrade - case 0xFD: // data transparent transmit finished - case 0xFE: // data transparent transmit ready + { + ESP_LOGD(TAG, "system successful start up %zu", to_process_length); + this->nextion_reports_is_setup_ = true; break; + } + case 0x89: { // start SD card upgrade + break; + } + // Data from nextion is + // 0x90 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x90: { // Switched component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad switch component data received for 0x90 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Switch variable_name=%s value=%d", variable_name.c_str(), to_process[0] != 0); + + for (auto *switchtype : this->switchtype_) { + switchtype->process_bool(variable_name, to_process[index] != 0); + } + break; + } + // Data from nextion is + // 0x91 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x71 return data: prints temp1.val,0 + // FF FF FF - End + case 0x91: { // Sensor component + std::string variable_name; + uint8_t index = 0; + + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) != 4) { + ESP_LOGE(TAG, "Bad sensor component data received for 0x91 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + index = to_process.find('\0'); + variable_name = to_process.substr(0, index); + // // Get variable name + int value = 0; + for (int i = 0; i < 4; ++i) { + value += to_process[i + index + 1] << (8 * i); + } + + ESP_LOGN(TAG, "Got sensor variable_name=%s value=%d", variable_name.c_str(), value); + + for (auto *sensor : this->sensortype_) { + sensor->process_sensor(variable_name, value); + } + break; + } + + // Data from nextion is + // 0x92 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // variable length of 0x70 return formatted data (bytes) that contain the text prints temp1.txt,0 + // 00 - NULL + // FF FF FF - End + case 0x92: { // Text Sensor Component + std::string variable_name; + std::string text_value; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad text sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + text_value = to_process.substr(index); + + ESP_LOGN(TAG, "Got Text Sensor variable_name=%s value=%s", variable_name.c_str(), text_value.c_str()); + + // NextionTextSensorResponseQueue *nq = new NextionTextSensorResponseQueue; + // nq->variable_name = variable_name; + // nq->state = text_value; + // this->textsensorq_.push_back(nq); + for (auto *textsensortype : this->textsensortype_) { + textsensortype->process_text(variable_name, text_value); + } + break; + } + // Data from nextion is + // 0x93 - Start + // variable length of 0x70 return formatted data (bytes) that contain the variable name: prints "temp1",0 + // 00 - NULL + // 00/01 - Single byte for on/off + // FF FF FF - End + case 0x93: { // Binary Sensor component + std::string variable_name; + uint8_t index = 0; + + // Get variable name + index = to_process.find('\0'); + if (static_cast(index) == std::string::npos || (to_process_length - index - 1) < 1) { + ESP_LOGE(TAG, "Bad binary sensor component data received for 0x92 event!"); + ESP_LOGN(TAG, "to_process %s %zu %d", to_process.c_str(), to_process_length, index); + break; + } + + variable_name = to_process.substr(0, index); + ++index; + + ESP_LOGN(TAG, "Got Binary Sensor variable_name=%s value=%d", variable_name.c_str(), to_process[index] != 0); + + for (auto *binarysensortype : this->binarysensortype_) { + binarysensortype->process_bool(&variable_name[0], to_process[index] != 0); + } + break; + } + case 0xFD: { // data transparent transmit finished + ESP_LOGVV(TAG, "Nextion reported data transmit finished!"); + break; + } + case 0xFE: { // data transparent transmit ready + ESP_LOGVV(TAG, "Nextion reported ready for transmit!"); + + int index = 0; + int found = -1; + for (auto &nb : this->nextion_queue_) { + auto component = nb->component; + if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { + size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() + : 255; // ADDT command can only send 255 + + this->write_array(component->get_wave_buffer().data(), static_cast(buffer_to_send)); + + ESP_LOGN(TAG, "Nextion sending waveform data for component id %d and waveform id %d, size %zu", + component->get_component_id(), component->get_wave_channel_id(), buffer_to_send); + + if (component->get_wave_buffer().size() <= 255) { + component->get_wave_buffer().clear(); + } else { + component->get_wave_buffer().erase(component->get_wave_buffer().begin(), + component->get_wave_buffer().begin() + buffer_to_send); + } + found = index; + delete component; + delete nb; + break; + } + ++index; + } + + if (found == -1) { + ESP_LOGE(TAG, "No waveforms in queue to send data!"); + break; + } else { + this->nextion_queue_.erase(this->nextion_queue_.begin() + found); + } + break; + } default: - ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", event); + ESP_LOGW(TAG, "Received unknown event from nextion: 0x%02X", this->nextion_event_); break; } - if (invalid_data_length) { - ESP_LOGW(TAG, "Invalid data length from nextion!"); + + // ESP_LOGN(TAG, "nextion_event_ deleting from 0 to %d", to_process_length + COMMAND_DELIMITER.length() + 1); + this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); + } + + uint32_t ms = millis(); + + if (!this->nextion_queue_.empty() && this->nextion_queue_.front()->queue_time + this->max_q_age_ms_ < ms) { + for (int i = 0; i < this->nextion_queue_.size(); i++) { + NextionComponentBase *component = this->nextion_queue_[i]->component; + if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { + if (this->nextion_queue_[i]->queue_time == 0) + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", + component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); + + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + + ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\"", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + if (component->get_queue_type() == NextionQueueType::NO_RESULT) { + if (component->get_variable_name() == "sleep_wake") { + this->is_sleeping_ = false; + } + delete component; + } + + delete this->nextion_queue_[i]; + + this->nextion_queue_.erase(this->nextion_queue_.begin() + i); + + } else { + break; + } + } + } + ESP_LOGN(TAG, "Loop End"); + // App.feed_wdt(); Remove before master merge + this->process_serial_(); +} // namespace nextion + +void Nextion::set_nextion_sensor_state(int queue_type, const std::string &name, float state) { + this->set_nextion_sensor_state(static_cast(queue_type), name, state); +} + +void Nextion::set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state) { + ESP_LOGN(TAG, "Received state for variable %s, state %lf for queue type %d", name.c_str(), state, queue_type); + + switch (queue_type) { + case NextionQueueType::SENSOR: { + for (auto *sensor : this->sensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } + break; + } + case NextionQueueType::BINARY_SENSOR: { + for (auto *sensor : this->binarysensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + case NextionQueueType::SWITCH: { + for (auto *sensor : this->switchtype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state != 0, true, true); + break; + } + } + break; + } + default: { + ESP_LOGW(TAG, "set_nextion_sensor_state does not support a queue type %d", queue_type); + } + } +} + +void Nextion::set_nextion_text_state(const std::string &name, const std::string &state) { + ESP_LOGD(TAG, "Received state for variable %s, state %s", name.c_str(), state.c_str()); + + for (auto *sensor : this->textsensortype_) { + if (name == sensor->get_variable_name()) { + sensor->set_state(state, true, true); + break; + } + } +} + +void Nextion::all_components_send_state_(bool force_update) { + ESP_LOGD(TAG, "all_components_send_state_ "); + for (auto *binarysensortype : this->binarysensortype_) { + if (force_update || binarysensortype->get_needs_to_send_update()) + binarysensortype->send_state_to_nextion(); + } + for (auto *sensortype : this->sensortype_) { + if ((force_update || sensortype->get_needs_to_send_update()) && sensortype->get_wave_chan_id() == 0) + sensortype->send_state_to_nextion(); + } + for (auto *switchtype : this->switchtype_) { + if (force_update || switchtype->get_needs_to_send_update()) + switchtype->send_state_to_nextion(); + } + for (auto *textsensortype : this->textsensortype_) { + if (force_update || textsensortype->get_needs_to_send_update()) + textsensortype->send_state_to_nextion(); + } +} + +void Nextion::update_components_by_prefix(const std::string &prefix) { + for (auto *binarysensortype : this->binarysensortype_) { + if (binarysensortype->get_variable_name().find(prefix, 0) != std::string::npos) + binarysensortype->update_component_settings(true); + } + for (auto *sensortype : this->sensortype_) { + if (sensortype->get_variable_name().find(prefix, 0) != std::string::npos) + sensortype->update_component_settings(true); + } + for (auto *switchtype : this->switchtype_) { + if (switchtype->get_variable_name().find(prefix, 0) != std::string::npos) + switchtype->update_component_settings(true); + } + for (auto *textsensortype : this->textsensortype_) { + if (textsensortype->get_variable_name().find(prefix, 0) != std::string::npos) + textsensortype->update_component_settings(true); + } +} + +uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag) { + uint16_t ret = 0; + uint8_t c = 0; + uint8_t nr_of_ff_bytes = 0; + uint64_t start; + bool exit_flag = false; + bool ff_flag = false; + + start = millis(); + + while ((timeout == 0 && this->available()) || millis() - start <= timeout) { + this->read_byte(&c); + if (c == 0xFF) + nr_of_ff_bytes++; + else { + nr_of_ff_bytes = 0; + ff_flag = false; + } + + if (nr_of_ff_bytes >= 3) + ff_flag = true; + + response += (char) c; + if (recv_flag) { + if (response.find(0x05) != std::string::npos) { + exit_flag = true; + } + } + App.feed_wdt(); + delay(1); + + if (exit_flag || ff_flag) { + break; } } - return false; + if (ff_flag) + response = response.substr(0, response.length() - 3); // Remove last 3 0xFF + + ret = response.length(); + return ret; } -void Nextion::loop() { - while (this->available() >= 4) { - this->read_until_ack_(); + +/** + * @brief + * + * @param variable_name Name for the queue + */ +void Nextion::add_no_result_to_queue_(const std::string &variable_name) { + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->component->set_variable_name(variable_name); + + nextion_queue->queue_time = millis(); + + this->nextion_queue_.push_back(nextion_queue); + + ESP_LOGN(TAG, "Add to queue type: NORESULT component %s", nextion_queue->component->get_variable_name().c_str()); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param command + */ +void Nextion::add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command) { + if ((!this->is_setup() && !this->ignore_is_setup_) || command.empty()) + return; + + if (this->send_command_(command)) { + this->add_no_result_to_queue_(variable_name); } } -#ifdef USE_TIME -void Nextion::set_nextion_rtc_time(time::ESPTime time) { - this->send_command_printf("rtc0=%u", time.year); - this->send_command_printf("rtc1=%u", time.month); - this->send_command_printf("rtc2=%u", time.day_of_month); - this->send_command_printf("rtc3=%u", time.hour); - this->send_command_printf("rtc4=%u", time.minute); - this->send_command_printf("rtc5=%u", time.second); -} -#endif -void Nextion::set_backlight_brightness(uint8_t brightness) { this->send_command_printf("dim=%u", brightness); } -void Nextion::set_touch_sleep_timeout(uint16_t timeout) { this->send_command_printf("thsp=%u", timeout); } +bool Nextion::add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, + ...) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return false; -void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } -void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + char buffer[256]; va_list arg; va_start(arg, format); - char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); va_end(arg); - if (ret > 0) - this->set_component_text(component, buffer); -} -void Nextion::set_wait_for_ack(bool wait_for_ack) { this->wait_for_ack_ = wait_for_ack; } + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } -void NextionTouchComponent::process(uint8_t page_id, uint8_t component_id, bool on) { - if (this->page_id_ == page_id && this->component_id_ == component_id) { - this->publish_state(on); + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief Sends a formatted command to the nextion + * + * @param variable_name Variable name for the queue + * @param format The printf-style command format, like "vis %s,0" + * @param ... The format arguments + */ +bool Nextion::add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return false; + + char buffer[256]; + va_list arg; + va_start(arg, format); + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret <= 0) { + ESP_LOGW(TAG, "Building command for format '%s' failed!", format); + return false; + } + + this->add_no_result_to_queue_with_command_(variable_name, buffer); + return true; +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ + +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} + +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_ignore_sleep_printf_(variable_name, "%s=%d", variable_name_to_send.c_str(), + state_value); +} + +/** + * @brief + * + * @param variable_name Variable name for the queue + * @param variable_name_to_send Variable name for the left of the command + * @param state_value Sting value to set + * @param is_sleep_safe The command is safe to send when the Nextion is sleeping + */ +void Nextion::add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) { + this->add_no_result_to_queue_with_set(component->get_variable_name(), component->get_variable_name_to_send(), + state_value); +} +void Nextion::add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) { + this->add_no_result_to_queue_with_set_internal_(variable_name, variable_name_to_send, state_value); +} + +void Nextion::add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe) { + if ((!this->is_setup() && !this->ignore_is_setup_) || (!is_sleep_safe && this->is_sleeping())) + return; + + this->add_no_result_to_queue_with_printf_(variable_name, "%s=\"%s\"", variable_name_to_send.c_str(), + state_value.c_str()); +} + +void Nextion::add_to_get_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_)) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = component; + nextion_queue->queue_time = millis(); + + ESP_LOGN(TAG, "Add to queue type: %s component %s", component->get_queue_type_string().c_str(), + component->get_variable_name().c_str()); + + std::string command = "get " + component->get_variable_name_to_send(); + + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); } } +/** + * @brief Add addt command to the queue + * + * @param component_id The waveform component id + * @param wave_chan_id The waveform channel to send it to + * @param buffer_to_send The buffer size + * @param buffer_size The buffer data + */ +void Nextion::add_addt_command_to_queue(NextionComponentBase *component) { + if ((!this->is_setup() && !this->ignore_is_setup_) || this->is_sleeping()) + return; + + nextion::NextionQueue *nextion_queue = new nextion::NextionQueue; + + nextion_queue->component = new nextion::NextionComponentBase; + nextion_queue->queue_time = millis(); + + size_t buffer_to_send = component->get_wave_buffer_size() < 255 ? component->get_wave_buffer_size() + : 255; // ADDT command can only send 255 + + std::string command = "addt " + to_string(component->get_component_id()) + "," + + to_string(component->get_wave_channel_id()) + "," + to_string(buffer_to_send); + if (this->send_command_(command)) { + this->nextion_queue_.push_back(nextion_queue); + } +} + +void Nextion::set_writer(const nextion_writer_t &writer) { this->writer_ = writer; } + +ESPDEPRECATED("set_wait_for_ack(bool) is deprecated and has no effect") +void Nextion::set_wait_for_ack(bool wait_for_ack) { ESP_LOGE(TAG, "This command is depreciated"); } + } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index a55ff747ee..2389cc6235 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1,9 +1,21 @@ #pragma once -#include "esphome/core/component.h" +#include #include "esphome/core/defines.h" #include "esphome/components/uart/uart.h" -#include "esphome/components/binary_sensor/binary_sensor.h" +#include "nextion_base.h" +#include "nextion_component.h" +#include "esphome/components/display/display_color_utils.h" + +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP32 +#include +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#include +#include +#endif +#endif #ifdef USE_TIME #include "esphome/components/time/real_time_clock.h" @@ -12,12 +24,14 @@ namespace esphome { namespace nextion { -class NextionTouchComponent; class Nextion; +class NextionComponentBase; using nextion_writer_t = std::function; -class Nextion : public PollingComponent, public uart::UARTDevice { +static const std::string COMMAND_DELIMITER{static_cast(255), static_cast(255), static_cast(255)}; + +class Nextion : public NextionBase, public PollingComponent, public uart::UARTDevice { public: /** * Set the text of a component to a static string. @@ -73,9 +87,20 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * This will change the image of the component `pic` to the image with ID `4`. */ - void set_component_picture(const char *component, const char *picture) { - this->send_command_printf("%s.val=%s", component, picture); - } + void set_component_picture(const char *component, const char *picture); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_background_color("button", 0xFF0000); + * ``` + * + * This will change the background color of the component `button` to red. + */ + void set_component_background_color(const char *component, uint32_t color); /** * Set the background color of a component. * @param component The component name. @@ -83,7 +108,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_background_color("button", "17013"); + * it.set_component_background_color("button", "RED"); * ``` * * This will change the background color of the component `button` to blue. @@ -91,6 +116,33 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_background_color(const char *component, const char *color); + /** + * Set the background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_background_color("button", color); + * ``` + * + * This will change the background color of the component `button` to what color contains. + */ + void set_component_background_color(const char *component, Color color) override; + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as a int). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", 0xFF0000 ); + * ``` + * + * This will change the pressed background color of the component `button` to red. This is the background color that + * is shown when the component is pressed. + */ + void set_component_pressed_background_color(const char *component, uint32_t color); /** * Set the pressed background color of a component. * @param component The component name. @@ -98,7 +150,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_background_color("button", "17013"); + * it.set_component_pressed_background_color("button", "RED"); * ``` * * This will change the pressed background color of the component `button` to blue. This is the background color that @@ -107,6 +159,63 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void set_component_pressed_background_color(const char *component, const char *color); + /** + * Set the pressed background color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_background_color("button", color); + * ``` + * + * This will change the pressed background color of the component `button` to blue. This is the background color that + * is shown when the component is pressed. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void set_component_pressed_background_color(const char *component, Color color) override; + + /** + * Set the picture id of a component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_pic("textview", 1); + * ``` + * + * This will change the picture id of the component `textview`. + */ + void set_component_pic(const char *component, uint8_t pic_id); + /** + * Set the background picture id of component. + * @param component The component name. + * @param pic_id The picture ID. + * + * Example: + * ```cpp + * it.set_component_picc("textview", 1); + * ``` + * + * This will change the background picture id of the component `textview`. + */ + void set_component_picc(const char *component, uint8_t pic_id); + + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t ). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", 0xFF0000); + * ``` + * + * This will change the font color of the component `textview` to a red color. + */ + void set_component_font_color(const char *component, uint32_t color); /** * Set the font color of a component. * @param component The component name. @@ -114,7 +223,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_font_color("textview", "17013"); + * it.set_component_font_color("textview", "RED"); * ``` * * This will change the font color of the component `textview` to a blue color. @@ -122,6 +231,34 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_font_color(const char *component, const char *color); + /** + * Set the font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_font_color("textview", color); + * ``` + * + * This will change the font color of the component `textview` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_font_color(const char *component, Color color) override; + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as a uint32_t). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", 0xFF0000); + * ``` + * + * This will change the pressed font color of the component `button` to a red. + */ + void set_component_pressed_font_color(const char *component, uint32_t color); /** * Set the pressed font color of a component. * @param component The component name. @@ -129,7 +266,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * it.set_component_pressed_font_color("button", "17013"); + * it.set_component_pressed_font_color("button", "RED"); * ``` * * This will change the pressed font color of the component `button` to a blue color. @@ -137,6 +274,21 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void set_component_pressed_font_color(const char *component, const char *color); + /** + * Set the pressed font color of a component. + * @param component The component name. + * @param color The color (as Color). + * + * Example: + * ```cpp + * it.set_component_pressed_font_color("button", color); + * ``` + * + * This will change the pressed font color of the component `button` to a blue color. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void set_component_pressed_font_color(const char *component, Color color) override; /** * Set the coordinates of a component on screen. * @param component The component name. @@ -163,7 +315,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Changes the font of the component named `textveiw`. Font IDs are set in the Nextion Editor. */ - void set_component_font(const char *component, uint8_t font_id); + void set_component_font(const char *component, uint8_t font_id) override; #ifdef USE_TIME /** * Send the current time to the nextion display. @@ -195,7 +347,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Hides the component named `button`. */ - void hide_component(const char *component); + void hide_component(const char *component) override; /** * Show a component. * @param component The component name. @@ -207,7 +359,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Shows the component named `button`. */ - void show_component(const char *component); + void show_component(const char *component) override; /** * Enable touch for a component. * @param component The component name. @@ -239,6 +391,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param value The value to write. */ void add_waveform_data(int component_id, uint8_t channel_number, uint8_t value); + void open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value); /** * Display a picture at coordinates. * @param picture_id The picture id. @@ -263,7 +416,7 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * * Example: * ```cpp - * fill_area(50, 50, 100, 100, "17013"); + * fill_area(50, 50, 100, 100, "RED"); * ``` * * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with @@ -271,6 +424,24 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * convert color codes to Nextion HMI colors */ void fill_area(int x1, int y1, int width, int height, const char *color); + /** + * Fill a rectangle with a color. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width to draw. + * @param height The height to draw. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * fill_area(50, 50, 100, 100, color); + * ``` + * + * Fills an area that starts at x coordiante `50` and y coordinate `50` with a height of `100` and width of `100` with + * the color of blue. Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to + * convert color codes to Nextion HMI colors + */ + void fill_area(int x1, int y1, int width, int height, Color color); /** * Draw a line on the screen. * @param x1 The starting x coordinate. @@ -290,6 +461,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void line(int x1, int y1, int x2, int y2, const char *color); + /** + * Draw a line on the screen. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param x2 The ending x coordinate. + * @param y2 The ending y coordinate. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.line(50, 50, 75, 75, "17013"); + * ``` + * + * Makes a line that starts at x coordinate `50` and y coordinate `50` and ends at x coordinate `75` and y coordinate + * `75` with the color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void line(int x1, int y1, int x2, int y2, Color color); /** * Draw a rectangle outline. * @param x1 The starting x coordinate. @@ -309,6 +499,25 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * colors. */ void rectangle(int x1, int y1, int width, int height, const char *color); + /** + * Draw a rectangle outline. + * @param x1 The starting x coordinate. + * @param y1 The starting y coordinate. + * @param width The width of the rectangle. + * @param height The height of the rectangle. + * @param color The color to draw with (as Color). + * + * Example: + * ```cpp + * it.rectangle(25, 35, 40, 50, "17013"); + * ``` + * + * Makes a outline of a rectangle that starts at x coordinate `25` and y coordinate `35` and has a width of `40` and a + * length of `50` with color of blue. Use this [color + * picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to Nextion HMI + * colors. + */ + void rectangle(int x1, int y1, int width, int height, Color color); /** * Draw a circle outline * @param center_x The center x coordinate. @@ -317,6 +526,14 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * @param color The color to draw with (as a string). */ void circle(int center_x, int center_y, int radius, const char *color); + /** + * Draw a circle outline + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). + */ + void circle(int center_x, int center_y, int radius, Color color); /** * Draw a filled circled. * @param center_x The center x coordinate. @@ -334,19 +551,36 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * Nextion HMI colors. */ void filled_circle(int center_x, int center_y, int radius, const char *color); - - /** Set the brightness of the backlight. - * - * @param brightness The brightness, from 0 to 100. + /** + * Draw a filled circled. + * @param center_x The center x coordinate. + * @param center_y The center y coordinate. + * @param radius The circle radius. + * @param color The color to draw with (as Color). * * Example: * ```cpp - * it.set_backlight_brightness(30); + * it.filled_cricle(25, 25, 10, color); + * ``` + * + * Makes a filled circle at the x cordinates `25` and y coordinate `25` with a radius of `10` with a color of blue. + * Use this [color picker](https://nodtem66.github.io/nextion-hmi-color-convert/index.html) to convert color codes to + * Nextion HMI colors. + */ + void filled_circle(int center_x, int center_y, int radius, Color color); + + /** Set the brightness of the backlight. + * + * @param brightness The brightness percentage from 0 to 1.0. + * + * Example: + * ```cpp + * it.set_backlight_brightness(.3); * ``` * * Changes the brightness of the display to 30%. */ - void set_backlight_brightness(uint8_t brightness); + void set_backlight_brightness(float brightness); /** * Set the touch sleep timeout of the display. * @param timeout Timeout in seconds. @@ -360,10 +594,46 @@ class Nextion : public PollingComponent, public uart::UARTDevice { * `thup`. */ void set_touch_sleep_timeout(uint16_t timeout); + /** + * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. + * @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to + * wakes up to current page. + * + * Example: + * ```cpp + * it.set_wake_up_page(2); + * ``` + * + * The display will wake up to page 2. + */ + void set_wake_up_page(uint8_t page_id = 255); + /** + * Sets if Nextion should auto-wake from sleep when touch press occurs. + * @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode, + * the first touch will only trigger the auto wake mode and not trigger a Touch Event. + * + * Example: + * ```cpp + * it.set_auto_wake_on_touch(true); + * ``` + * + * The display will wake up by touch. + */ + void set_auto_wake_on_touch(bool auto_wake); + /** + * Sets Nextion mode between sleep and awake + * @param True or false. Sleep=true to enter sleep mode or sleep=false to exit sleep mode. + */ + void sleep(bool sleep); // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) - void register_touch_component(NextionTouchComponent *obj) { this->touch_.push_back(obj); } + void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); } + void register_switch_component(NextionComponentBase *obj) { this->switchtype_.push_back(obj); } + void register_binarysensor_component(NextionComponentBase *obj) { this->binarysensortype_.push_back(obj); } + void register_sensor_component(NextionComponentBase *obj) { this->sensortype_.push_back(obj); } + void register_textsensor_component(NextionComponentBase *obj) { this->textsensortype_.push_back(obj); } + void setup() override; void set_brightness(float brightness) { this->brightness_ = brightness; } float get_setup_priority() const override; @@ -371,11 +641,9 @@ class Nextion : public PollingComponent, public uart::UARTDevice { void loop() override; void set_writer(const nextion_writer_t &writer); - /** - * Manually send a raw command to the display and don't wait for an acknowledgement packet. - * @param command The command to write, for example "vis b0,0". - */ - void send_command_no_ack(const char *command); + // This function has been deprecated + void set_wait_for_ack(bool wait_for_ack); + /** * Manually send a raw formatted command to the display. * @param format The printf-style command format, like "vis %s,0" @@ -384,28 +652,199 @@ class Nextion : public PollingComponent, public uart::UARTDevice { */ bool send_command_printf(const char *format, ...) __attribute__((format(printf, 2, 3))); - void set_wait_for_ack(bool wait_for_ack); +#ifdef USE_TFT_UPLOAD + /** + * Set the tft file URL. https seems problamtic with arduino.. + */ + void set_tft_url(const std::string &tft_url) { this->tft_url_ = tft_url; } + +#endif + + /** + * Upload the tft file and softreset the Nextion + */ + void upload_tft(); + void dump_config() override; + + /** + * Softreset the Nextion + */ + void soft_reset(); + + /** Add a callback to be notified of sleep state changes. + * + * @param callback The void() callback. + */ + void add_sleep_state_callback(std::function &&callback); + + /** Add a callback to be notified of wake state changes. + * + * @param callback The void() callback. + */ + void add_wake_state_callback(std::function &&callback); + + /** Add a callback to be notified when the nextion completes its initialize setup. + * + * @param callback The void() callback. + */ + void add_setup_state_callback(std::function &&callback); + + void update_all_components(); + + /** + * @brief Set the nextion sensor state object. + * + * @param[in] queue_type + * Index of NextionQueueType. + * + * @param[in] name + * Component/variable name. + * + * @param[in] state + * State to set. + */ + void set_nextion_sensor_state(int queue_type, const std::string &name, float state); + void set_nextion_sensor_state(NextionQueueType queue_type, const std::string &name, float state); + void set_nextion_text_state(const std::string &name, const std::string &state); + + void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + int state_value) override; + + void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) override; + void add_no_result_to_queue_with_set(const std::string &variable_name, const std::string &variable_name_to_send, + const std::string &state_value) override; + + void add_to_get_queue(NextionComponentBase *component) override; + + void add_addt_command_to_queue(NextionComponentBase *component) override; + + void update_components_by_prefix(const std::string &prefix); + + void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) { + this->touch_sleep_timeout_ = touch_sleep_timeout; + } + void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; } + void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; } protected: - bool ack_(); - bool read_until_ack_(); + std::deque nextion_queue_; + uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag); + void all_components_send_state_(bool force_update = false); + uint64_t comok_sent_ = 0; + bool remove_from_q_(bool report_empty = true); + /** + * @brief + * Sends commands ignoring of the Nextion has been setup. + */ + bool ignore_is_setup_ = false; + bool nextion_reports_is_setup_ = false; + uint8_t nextion_event_; + + void process_nextion_commands_(); + void process_serial_(); + bool is_updating_ = false; + uint32_t touch_sleep_timeout_ = 0; + int wake_up_page_ = -1; + bool auto_wake_on_touch_ = true; + + /** + * Manually send a raw command to the display and don't wait for an acknowledgement packet. + * @param command The command to write, for example "vis b0,0". + */ + bool send_command_(const std::string &command); + void add_no_result_to_queue_(const std::string &variable_name); + bool add_no_result_to_queue_with_ignore_sleep_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + void add_no_result_to_queue_with_command_(const std::string &variable_name, const std::string &command); + + bool add_no_result_to_queue_with_printf_(const std::string &variable_name, const char *format, ...) + __attribute__((format(printf, 3, 4))); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value, + bool is_sleep_safe = false); + + void add_no_result_to_queue_with_set_internal_(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value, bool is_sleep_safe = false); + +#ifdef USE_TFT_UPLOAD +#if defined(USE_ETHERNET) || defined(USE_WIFI) +#ifdef ARDUINO_ARCH_ESP8266 + WiFiClient *wifi_client_{nullptr}; + BearSSL::WiFiClientSecure *wifi_client_secure_{nullptr}; + WiFiClient *get_wifi_client_(); +#endif + + /** + * will request chunk_size chunks from the web server + * and send each to the nextion + * @param int contentLength Total size of the file + * @param uint32_t chunk_size + * @return true if success, false for failure. + */ + int content_length_ = 0; + int tft_size_ = 0; + int upload_by_chunks_(HTTPClient *http, int range_start); + + bool upload_with_range_(uint32_t range_start, uint32_t range_end); + + /** + * start update tft file to nextion. + * + * @param const uint8_t *file_buf + * @param size_t buf_size + * @return true if success, false for failure. + */ + bool upload_from_buffer_(const uint8_t *file_buf, size_t buf_size); + void upload_end_(); + +#endif + +#endif + + bool get_is_connected_() { return this->is_connected_; } + + bool check_connect_(); + + std::vector touch_; + std::vector switchtype_; + std::vector sensortype_; + std::vector textsensortype_; + std::vector binarysensortype_; + CallbackManager setup_callback_{}; + CallbackManager sleep_callback_{}; + CallbackManager wake_callback_{}; - std::vector touch_; optional writer_; - bool wait_for_ack_{true}; float brightness_{1.0}; + + std::string device_model_; + std::string firmware_version_; + std::string serial_number_; + std::string flash_size_; + + void remove_front_no_sensors_(); + +#ifdef USE_TFT_UPLOAD + std::string tft_url_; + uint8_t *transfer_buffer_{nullptr}; + size_t transfer_buffer_size_; + bool upload_first_chunk_sent_ = false; +#endif + +#ifdef NEXTION_PROTOCOL_LOG + void print_queue_members_(); +#endif + void reset_(bool reset_nextion = true); + + std::string command_data_; + bool is_connected_ = false; + uint32_t startup_override_ms_ = 8000; + uint32_t max_q_age_ms_ = 8000; + uint32_t started_ms_ = 0; + bool sent_setup_commands_ = false; }; - -class NextionTouchComponent : public binary_sensor::BinarySensorInitiallyOff { - public: - void set_page_id(uint8_t page_id) { page_id_ = page_id; } - void set_component_id(uint8_t component_id) { component_id_ = component_id; } - void process(uint8_t page_id, uint8_t component_id, bool on); - - protected: - uint8_t page_id_; - uint8_t component_id_; -}; - } // namespace nextion } // namespace esphome diff --git a/esphome/components/nextion/nextion_base.h b/esphome/components/nextion/nextion_base.h new file mode 100644 index 0000000000..a24fd74060 --- /dev/null +++ b/esphome/components/nextion/nextion_base.h @@ -0,0 +1,58 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_component_base.h" +namespace esphome { +namespace nextion { + +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define NEXTION_PROTOCOL_LOG +#endif + +#ifdef NEXTION_PROTOCOL_LOG +#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE +#define ESP_LOGN(tag, ...) ESP_LOGVV(tag, __VA_ARGS__) +#else +#define ESP_LOGN(tag, ...) ESP_LOGD(tag, __VA_ARGS__) +#endif +#else +#define ESP_LOGN(tag, ...) \ + {} +#endif + +class NextionBase; + +class NextionBase { + public: + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, int state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, int state_value) = 0; + + virtual void add_no_result_to_queue_with_set(NextionComponentBase *component, const std::string &state_value) = 0; + virtual void add_no_result_to_queue_with_set(const std::string &variable_name, + const std::string &variable_name_to_send, + const std::string &state_value) = 0; + + virtual void add_addt_command_to_queue(NextionComponentBase *component) = 0; + + virtual void add_to_get_queue(NextionComponentBase *component) = 0; + + virtual void set_component_background_color(const char *component, Color color) = 0; + virtual void set_component_pressed_background_color(const char *component, Color color) = 0; + virtual void set_component_font_color(const char *component, Color color) = 0; + virtual void set_component_pressed_font_color(const char *component, Color color) = 0; + virtual void set_component_font(const char *component, uint8_t font_id) = 0; + + virtual void show_component(const char *component) = 0; + virtual void hide_component(const char *component) = 0; + + bool is_sleeping() { return this->is_sleeping_; } + bool is_setup() { return this->is_setup_; } + + protected: + bool is_setup_ = false; + bool is_sleeping_ = false; +}; + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp new file mode 100644 index 0000000000..931b934ba2 --- /dev/null +++ b/esphome/components/nextion/nextion_commands.cpp @@ -0,0 +1,234 @@ +#include "nextion.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion"; + +// Sleep safe commands +void Nextion::soft_reset() { this->send_command_("rest"); } + +void Nextion::set_wake_up_page(uint8_t page_id) { + if (page_id > 255) { + ESP_LOGD(TAG, "Wake up page of bounds, range 0-255"); + return; + } + this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true); +} + +void Nextion::set_touch_sleep_timeout(uint16_t timeout) { + if (timeout < 3 || timeout > 65535) { + ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535"); + return; + } + + this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true); +} + +void Nextion::sleep(bool sleep) { + if (sleep) { // Set sleep + this->is_sleeping_ = true; + this->add_no_result_to_queue_with_set_internal_("sleep", "sleep", 1, true); + } else { // Turn off sleep. Wait for a sleep_wake return before setting sleep off + this->add_no_result_to_queue_with_set_internal_("sleep_wake", "sleep", 0, true); + } +} +// End sleep safe commands + +// Set Colors +void Nextion::set_component_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, color); +} + +void Nextion::set_component_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%s", component, color); +} + +void Nextion::set_component_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_background_color", "%s.bco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_background_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%s", component, color); +} + +void Nextion::set_component_pressed_background_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_background_color", "%s.bco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pic(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.pic=%d", component, pic_id); +} + +void Nextion::set_component_picc(const char *component, uint8_t pic_id) { + this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%d", component, pic_id); +} + +void Nextion::set_component_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, color); +} + +void Nextion::set_component_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%s", component, color); +} + +void Nextion::set_component_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_font_color", "%s.pco=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_pressed_font_color(const char *component, uint32_t color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, const char *color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color); +} + +void Nextion::set_component_pressed_font_color(const char *component, Color color) { + this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%d", component, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::set_component_text_printf(const char *component, const char *format, ...) { + va_list arg; + va_start(arg, format); + char buffer[256]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + va_end(arg); + if (ret > 0) + this->set_component_text(component, buffer); +} + +// General Nextion +void Nextion::goto_page(const char *page) { this->add_no_result_to_queue_with_printf_("goto_page", "page %s", page); } + +void Nextion::set_backlight_brightness(float brightness) { + if (brightness < 0 || brightness > 1.0) { + ESP_LOGD(TAG, "Brightness out of bounds, percentage range 0-1.0"); + return; + } + this->add_no_result_to_queue_with_set("backlight_brightness", "dim", static_cast(brightness * 100)); +} + +void Nextion::set_auto_wake_on_touch(bool auto_wake) { + this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0); +} + +// General Component +void Nextion::set_component_font(const char *component, uint8_t font_id) { + this->add_no_result_to_queue_with_printf_("set_component_font", "%s.font=%d", component, font_id); +} + +void Nextion::hide_component(const char *component) { + this->add_no_result_to_queue_with_printf_("hide_component", "vis %s,0", component); +} + +void Nextion::show_component(const char *component) { + this->add_no_result_to_queue_with_printf_("show_component", "vis %s,1", component); +} + +void Nextion::enable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("enable_component_touch", "tsw %s,1", component); +} + +void Nextion::disable_component_touch(const char *component) { + this->add_no_result_to_queue_with_printf_("disable_component_touch", "tsw %s,0", component); +} + +void Nextion::set_component_picture(const char *component, const char *picture) { + this->add_no_result_to_queue_with_printf_("set_component_picture", "%s.val=%s", component, picture); +} + +void Nextion::set_component_text(const char *component, const char *text) { + this->add_no_result_to_queue_with_printf_("set_component_text", "%s.txt=\"%s\"", component, text); +} + +void Nextion::set_component_value(const char *component, int value) { + this->add_no_result_to_queue_with_printf_("set_component_value", "%s.val=%d", component, value); +} + +void Nextion::add_waveform_data(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("add_waveform_data", "add %d,%u,%u", component_id, channel_number, value); +} + +void Nextion::open_waveform_channel(int component_id, uint8_t channel_number, uint8_t value) { + this->add_no_result_to_queue_with_printf_("open_waveform_channel", "addt %d,%u,%u", component_id, channel_number, + value); +} + +void Nextion::set_component_coordinates(const char *component, int x, int y) { + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 1", "%s.xcen=%d", component, x); + this->add_no_result_to_queue_with_printf_("set_component_coordinates command 2", "%s.ycen=%d", component, y); +} + +// Drawing +void Nextion::display_picture(int picture_id, int x_start, int y_start) { + this->add_no_result_to_queue_with_printf_("display_picture", "pic %d %d %d", x_start, y_start, picture_id); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%s", x1, y1, width, height, color); +} + +void Nextion::fill_area(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("fill_area", "fill %d,%d,%d,%d,%d", x1, y1, width, height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::line(int x1, int y1, int x2, int y2, const char *color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%s", x1, y1, x2, y2, color); +} + +void Nextion::line(int x1, int y1, int x2, int y2, Color color) { + this->add_no_result_to_queue_with_printf_("line", "line %d,%d,%d,%d,%d", x1, y1, x2, y2, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, const char *color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%s", x1, y1, x1 + width, y1 + height, color); +} + +void Nextion::rectangle(int x1, int y1, int width, int height, Color color) { + this->add_no_result_to_queue_with_printf_("draw", "draw %d,%d,%d,%d,%d", x1, y1, x1 + width, y1 + height, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cir", "cir %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, const char *color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%s", center_x, center_y, radius, color); +} + +void Nextion::filled_circle(int center_x, int center_y, int radius, Color color) { + this->add_no_result_to_queue_with_printf_("cirs", "cirs %d,%d,%d,%d", center_x, center_y, radius, + display::ColorUtil::color_to_565(color)); +} + +#ifdef USE_TIME +void Nextion::set_nextion_rtc_time(time::ESPTime time) { + this->add_no_result_to_queue_with_printf_("rtc0", "rtc0=%u", time.year); + this->add_no_result_to_queue_with_printf_("rtc1", "rtc1=%u", time.month); + this->add_no_result_to_queue_with_printf_("rtc2", "rtc2=%u", time.day_of_month); + this->add_no_result_to_queue_with_printf_("rtc3", "rtc3=%u", time.hour); + this->add_no_result_to_queue_with_printf_("rtc4", "rtc4=%u", time.minute); + this->add_no_result_to_queue_with_printf_("rtc5", "rtc5=%u", time.second); +} +#endif + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.cpp b/esphome/components/nextion/nextion_component.cpp new file mode 100644 index 0000000000..bbb2cf6cb2 --- /dev/null +++ b/esphome/components/nextion/nextion_component.cpp @@ -0,0 +1,116 @@ +#include "nextion_component.h" + +namespace esphome { +namespace nextion { + +void NextionComponent::set_background_color(Color bco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->bco_ = bco; + this->bco_needs_update_ = true; + this->bco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_background_pressed_color(Color bco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + + this->bco2_ = bco2; + this->bco2_needs_update_ = true; + this->bco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_color(Color pco) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco_ = pco; + this->pco_needs_update_ = true; + this->pco_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_foreground_pressed_color(Color pco2) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->pco2_ = pco2; + this->pco2_needs_update_ = true; + this->pco2_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_font_id(uint8_t font_id) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->font_id_ = font_id; + this->font_id_needs_update_ = true; + this->font_id_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::set_visible(bool visible) { + if (this->variable_name_ == this->variable_name_to_send_) { + return; // This is a variable. no need to set color + } + this->visible_ = visible; + this->visible_needs_update_ = true; + this->visible_is_set_ = true; + this->update_component_settings(); +} + +void NextionComponent::update_component_settings(bool force_update) { + if (this->nextion_->is_sleeping() || !this->nextion_->is_setup() || !this->visible_is_set_ || + (!this->visible_needs_update_ && !this->visible_)) { + this->needs_to_send_update_ = true; + return; + } + + if (this->visible_needs_update_ || (force_update && this->visible_is_set_)) { + std::string name_to_send = this->variable_name_; + + size_t pos = name_to_send.find_last_of('.'); + if (pos != std::string::npos) { + name_to_send = name_to_send.substr(pos + 1); + } + + this->visible_needs_update_ = false; + + if (this->visible_) { + this->nextion_->show_component(name_to_send.c_str()); + this->send_state_to_nextion(); + } else { + this->nextion_->hide_component(name_to_send.c_str()); + return; + } + } + + if (this->bco_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_background_color(this->variable_name_.c_str(), this->bco_); + this->bco_needs_update_ = false; + } + if (this->bco2_needs_update_ || (force_update && this->bco2_is_set_)) { + this->nextion_->set_component_pressed_background_color(this->variable_name_.c_str(), this->bco2_); + this->bco2_needs_update_ = false; + } + if (this->pco_needs_update_ || (force_update && this->pco_is_set_)) { + this->nextion_->set_component_font_color(this->variable_name_.c_str(), this->pco_); + this->pco_needs_update_ = false; + } + if (this->pco2_needs_update_ || (force_update && this->pco2_is_set_)) { + this->nextion_->set_component_pressed_font_color(this->variable_name_.c_str(), this->pco2_); + this->pco2_needs_update_ = false; + } + + if (this->font_id_needs_update_ || (force_update && this->font_id_is_set_)) { + this->nextion_->set_component_font(this->variable_name_.c_str(), this->font_id_); + this->font_id_needs_update_ = false; + } +} +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component.h b/esphome/components/nextion/nextion_component.h new file mode 100644 index 0000000000..2f3c4f3c16 --- /dev/null +++ b/esphome/components/nextion/nextion_component.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/color.h" +#include "nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionComponent; + +class NextionComponent : public NextionComponentBase { + public: + void update_component_settings() override { this->update_component_settings(false); }; + + void update_component_settings(bool force_update) override; + + void set_background_color(Color bco); + void set_background_pressed_color(Color bco2); + void set_foreground_color(Color pco); + void set_foreground_pressed_color(Color pco2); + void set_font_id(uint8_t font_id); + void set_visible(bool visible); + + protected: + NextionBase *nextion_; + + bool bco_needs_update_ = false; + bool bco_is_set_ = false; + Color bco_; + bool bco2_needs_update_ = false; + bool bco2_is_set_ = false; + Color bco2_; + bool pco_needs_update_ = false; + bool pco_is_set_ = false; + Color pco_; + bool pco2_needs_update_ = false; + bool pco2_is_set_ = false; + Color pco2_; + uint8_t font_id_ = 0; + bool font_id_needs_update_ = false; + bool font_id_is_set_ = false; + + bool visible_ = true; + bool visible_needs_update_ = false; + bool visible_is_set_ = false; + + // void send_state_to_nextion() = 0; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_component_base.h b/esphome/components/nextion/nextion_component_base.h new file mode 100644 index 0000000000..71ad803bc4 --- /dev/null +++ b/esphome/components/nextion/nextion_component_base.h @@ -0,0 +1,95 @@ +#pragma once +#include +#include "esphome/core/defines.h" + +namespace esphome { +namespace nextion { + +enum NextionQueueType { + NO_RESULT = 0, + SENSOR = 1, + BINARY_SENSOR = 2, + SWITCH = 3, + TEXT_SENSOR = 4, + WAVEFORM_SENSOR = 5, +}; + +static const char *const NEXTION_QUEUE_TYPE_STRINGS[] = {"NO_RESULT", "SENSOR", "BINARY_SENSOR", + "SWITCH", "TEXT_SENSOR", "WAVEFORM_SENSOR"}; + +class NextionComponentBase; + +class NextionQueue { + public: + virtual ~NextionQueue() = default; + NextionComponentBase *component; + uint32_t queue_time = 0; +}; + +class NextionComponentBase { + public: + virtual ~NextionComponentBase() = default; + + void set_variable_name(const std::string &variable_name, const std::string &variable_name_to_send = "") { + variable_name_ = variable_name; + if (variable_name_to_send.empty()) { + variable_name_to_send_ = variable_name; + } else { + variable_name_to_send_ = variable_name_to_send; + } + } + + virtual void update_component_settings(){}; + virtual void update_component_settings(bool force_update){}; + + virtual void update_component(){}; + virtual void process_sensor(const std::string &variable_name, int state){}; + virtual void process_touch(uint8_t page_id, uint8_t component_id, bool on){}; + virtual void process_text(const std::string &variable_name, const std::string &text_value){}; + virtual void process_bool(const std::string &variable_name, bool on){}; + + virtual void set_state(float state){}; + virtual void set_state(float state, bool publish){}; + virtual void set_state(float state, bool publish, bool send_to_nextion){}; + + virtual void set_state(bool state){}; + virtual void set_state(bool state, bool publish){}; + virtual void set_state(bool state, bool publish, bool send_to_nextion){}; + + virtual void set_state(const std::string &state) {} + virtual void set_state(const std::string &state, bool publish) {} + virtual void set_state(const std::string &state, bool publish, bool send_to_nextion){}; + + uint8_t get_component_id() { return this->component_id_; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + + uint8_t get_wave_channel_id() { return this->wave_chan_id_; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + + std::vector get_wave_buffer() { return this->wave_buffer_; } + size_t get_wave_buffer_size() { return this->wave_buffer_.size(); } + + std::string get_variable_name() { return this->variable_name_; } + std::string get_variable_name_to_send() { return this->variable_name_to_send_; } + virtual NextionQueueType get_queue_type() { return NextionQueueType::NO_RESULT; } + virtual std::string get_queue_type_string() { return NEXTION_QUEUE_TYPE_STRINGS[this->get_queue_type()]; } + virtual void set_state_from_int(int state_value, bool publish, bool send_to_nextion){}; + virtual void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion){}; + virtual void send_state_to_nextion(){}; + bool get_needs_to_send_update() { return this->needs_to_send_update_; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + + protected: + std::string variable_name_; + std::string variable_name_to_send_; + + uint8_t component_id_ = 0; + uint8_t wave_chan_id_ = UINT8_MAX; + std::vector wave_buffer_; + int wave_max_length_ = 255; + + bool needs_to_send_update_; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp new file mode 100644 index 0000000000..6a681af6c5 --- /dev/null +++ b/esphome/components/nextion/nextion_upload.cpp @@ -0,0 +1,343 @@ + +#include "nextion.h" +#include "esphome/core/application.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_upload"; + +#if defined(USE_TFT_UPLOAD) && (defined(USE_ETHERNET) || defined(USE_WIFI)) + +// Followed guide +// https://unofficialnextion.com/t/nextion-upload-protocol-v1-2-the-fast-one/1044/2 + +int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { + int range_end = 0; + + if (range_start == 0 && this->transfer_buffer_size_ > 16384) { // Start small at the first run in case of a big skip + range_end = 16384 - 1; + } else { + range_end = range_start + this->transfer_buffer_size_ - 1; + } + + if (range_end > this->tft_size_) + range_end = this->tft_size_; + + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http->setRedirectLimit(3); + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + char range_header[64]; + sprintf(range_header, "bytes=%d-%d", range_start, range_end); + + ESP_LOGD(TAG, "Requesting range: %s", range_header); + + int tries = 1; + int code = 0; + while (tries <= 5) { +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http->begin(this->tft_url_.c_str()); +#endif +#ifndef CLANG_TIDY +#ifdef ARDUINO_ARCH_ESP8266 + begin_status = http->begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + ++tries; + if (!begin_status) { + ESP_LOGD(TAG, "upload_by_chunks_: connection failed"); + continue; + } + + http->addHeader("Range", range_header); + + code = http->GET(); + if (code == 200 || code == 206) { + break; + } + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retries(%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + http->end(); + App.feed_wdt(); + delay(500); // NOLINT + } + + if (tries > 5) { + return -1; + } + + std::string recv_string; + size_t size = 0; + int sent = 0; + int range = range_end - range_start; + + while (sent < range) { + size = http->getStreamPtr()->available(); + if (!size) { + App.feed_wdt(); + delay(0); + continue; + } + int c = http->getStreamPtr()->readBytes( + &this->transfer_buffer_[sent], ((size > this->transfer_buffer_size_) ? this->transfer_buffer_size_ : size)); + sent += c; + } + http->end(); + ESP_LOGN(TAG, "this->content_length_ %d sent %d", this->content_length_, sent); + for (uint32_t i = 0; i < range; i += 4096) { + this->write_array(&this->transfer_buffer_[i], 4096); + this->content_length_ -= 4096; + ESP_LOGN(TAG, "this->content_length_ %d range %d range_end %d range_start %d", this->content_length_, range, + range_end, range_start); + + if (!this->upload_first_chunk_sent_) { + this->upload_first_chunk_sent_ = true; + delay(500); // NOLINT + App.feed_wdt(); + } + + this->recv_ret_string_(recv_string, 2048, true); + if (recv_string[0] == 0x08) { + uint32_t result = 0; + for (int i = 0; i < 4; ++i) { + result += static_cast(recv_string[i + 1]) << (8 * i); + } + if (result > 0) { + ESP_LOGD(TAG, "Nextion reported new range %d", result); + this->content_length_ = this->tft_size_ - result; + return result; + } + } + recv_string.clear(); + } + return range_end + 1; +} + +void Nextion::upload_tft() { + if (this->is_updating_) { + ESP_LOGD(TAG, "Currently updating"); + return; + } + + if (!network_is_connected()) { + ESP_LOGD(TAG, "network is not connected"); + return; + } + + this->is_updating_ = true; + + HTTPClient http; + http.setTimeout(15000); // Yes 15 seconds.... Helps 8266s along + bool begin_status = false; +#ifdef ARDUINO_ARCH_ESP32 + begin_status = http.begin(this->tft_url_.c_str()); +#endif +#ifdef ARDUINO_ARCH_ESP8266 +#ifndef CLANG_TIDY + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setRedirectLimit(3); + begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); +#endif +#endif + + if (!begin_status) { + this->is_updating_ = false; + ESP_LOGD(TAG, "connection failed"); +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) + free(this->transfer_buffer_); + else +#endif + delete this->transfer_buffer_; + return; + } else { + ESP_LOGD(TAG, "Connected"); + } + + http.addHeader("Range", "bytes=0-255"); + const char *header_names[] = {"Content-Range"}; + http.collectHeaders(header_names, 1); + ESP_LOGD(TAG, "Requesting URL: %s", this->tft_url_.c_str()); + + http.setReuse(true); + // try up to 5 times. DNS sometimes needs a second try or so + int tries = 1; + int code = http.GET(); + delay(100); // NOLINT + + App.feed_wdt(); + while (code != 200 && code != 206 && tries <= 5) { + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s, retrying (%d/5)", this->tft_url_.c_str(), + HTTPClient::errorToString(code).c_str(), tries); + + delay(250); // NOLINT + App.feed_wdt(); + code = http.GET(); + ++tries; + } + + if ((code != 200 && code != 206) || tries > 5) { + this->upload_end_(); + } + + String content_range_string = http.header("Content-Range"); + content_range_string.remove(0, 12); + this->content_length_ = content_range_string.toInt(); + this->tft_size_ = content_length_; + http.end(); + + if (this->content_length_ < 4096) { + ESP_LOGE(TAG, "Failed to get file size"); + this->upload_end_(); + } + + ESP_LOGD(TAG, "Updating Nextion %s...", this->device_model_.c_str()); + // The Nextion will ignore the update command if it is sleeping + + this->send_command_("sleep=0"); + this->set_backlight_brightness(1.0); + delay(250); // NOLINT + + App.feed_wdt(); + + char command[128]; + // Tells the Nextion the content length of the tft file and baud rate it will be sent at + // Once the Nextion accepts the command it will wait until the file is successfully uploaded + // If it fails for any reason a power cycle of the display will be needed + sprintf(command, "whmi-wris %d,%d,1", this->content_length_, this->parent_->get_baud_rate()); + + // Clear serial receive buffer + uint8_t d; + while (this->available()) { + this->read_byte(&d); + }; + + this->send_command_(command); + + App.feed_wdt(); + + std::string response; + ESP_LOGD(TAG, "Waiting for upgrade response"); + this->recv_ret_string_(response, 2000, true); // This can take some time to return + + // The Nextion display will, if it's ready to accept data, send a 0x05 byte. + ESP_LOGD(TAG, "Upgrade response is %s %zu", response.c_str(), response.length()); + + for (int i = 0; i < response.length(); i++) { + ESP_LOGD(TAG, "Available %d : 0x%02X", i, response[i]); + } + + if (response.find(0x05) != std::string::npos) { + ESP_LOGD(TAG, "preparation for tft update done"); + } else { + ESP_LOGD(TAG, "preparation for tft update failed %d \"%s\"", response[0], response.c_str()); + this->upload_end_(); + } + + // Nextion wants 4096 bytes at a time. Make chunk_size a multiple of 4096 +#ifdef ARDUINO_ARCH_ESP32 + uint32_t chunk_size = 8192; + if (psramFound()) { + chunk_size = this->content_length_; + } else { + if (ESP.getFreeHeap() > 40960) { // 32K to keep on hand + int chunk = int((ESP.getFreeHeap() - 32768) / 4096); + chunk_size = chunk * 4096; + chunk_size = chunk_size > 65536 ? 65536 : chunk_size; + } else if (ESP.getFreeHeap() < 10240) { + chunk_size = 4096; + } + } +#else + uint32_t chunk_size = ESP.getFreeHeap() < 10240 ? 4096 : 8192; +#endif + + if (this->transfer_buffer_ == nullptr) { +#ifdef ARDUINO_ARCH_ESP32 + if (psramFound()) { + ESP_LOGD(TAG, "Allocating PSRAM buffer size %d, Free PSRAM size is %u", chunk_size, ESP.getFreePsram()); + this->transfer_buffer_ = (uint8_t *) ps_malloc(chunk_size); + if (this->transfer_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer size %d!", chunk_size); + this->upload_end_(); + } + } else { +#endif + ESP_LOGD(TAG, "Allocating buffer size %d, Heap size is %u", chunk_size, ESP.getFreeHeap()); + this->transfer_buffer_ = new uint8_t[chunk_size]; + if (!this->transfer_buffer_) { // Try a smaller size + ESP_LOGD(TAG, "Could not allocate buffer size: %d trying 4096 instead", chunk_size); + chunk_size = 4096; + ESP_LOGD(TAG, "Allocating %d buffer", chunk_size); + this->transfer_buffer_ = new uint8_t[chunk_size]; + + if (!this->transfer_buffer_) + this->upload_end_(); +#ifdef ARDUINO_ARCH_ESP32 + } +#endif + } + + this->transfer_buffer_size_ = chunk_size; + } + + ESP_LOGD(TAG, "Updating tft from \"%s\" with a file size of %d using %zu chunksize, Heap Size %d", + this->tft_url_.c_str(), this->content_length_, this->transfer_buffer_size_, ESP.getFreeHeap()); + + int result = 0; + while (this->content_length_ > 0) { + result = this->upload_by_chunks_(&http, result); + if (result < 0) { + ESP_LOGD(TAG, "Error updating Nextion!"); + this->upload_end_(); + } + App.feed_wdt(); + ESP_LOGD(TAG, "Heap Size %d, Bytes left %d", ESP.getFreeHeap(), this->content_length_); + } + ESP_LOGD(TAG, "Succesfully updated Nextion!"); + + this->upload_end_(); +} + +void Nextion::upload_end_() { + ESP_LOGD(TAG, "Restarting Nextion"); + this->soft_reset(); + delay(1500); // NOLINT + ESP_LOGD(TAG, "Restarting esphome"); + ESP.restart(); +} + +#ifdef ARDUINO_ARCH_ESP8266 +WiFiClient *Nextion::get_wifi_client_() { + if (this->tft_url_.compare(0, 6, "https:") == 0) { + if (this->wifi_client_secure_ == nullptr) { + this->wifi_client_secure_ = new BearSSL::WiFiClientSecure(); + this->wifi_client_secure_->setInsecure(); + this->wifi_client_secure_->setBufferSizes(512, 512); + } + return this->wifi_client_secure_; + } + + if (this->wifi_client_ == nullptr) { + this->wifi_client_ = new WiFiClient(); + } + return this->wifi_client_; +} +#endif + +#else +void Nextion::upload_tft() { ESP_LOGW(TAG, "tft_url, WIFI or Ethernet components are needed. Cannot upload."); } +#endif +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py new file mode 100644 index 0000000000..f8a383e3ac --- /dev/null +++ b/esphome/components/nextion/sensor/__init__.py @@ -0,0 +1,99 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor + +from esphome.const import ( + CONF_ID, + UNIT_EMPTY, + ICON_EMPTY, + CONF_COMPONENT_ID, + DEVICE_CLASS_EMPTY, +) +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_SENSOR_COMPONENT_SCHEMA, + CONF_VARIABLE_NAME, + CONF_COMPONENT_NAME, + CONF_PRECISION, + CONF_WAVE_CHANNEL_ID, + CONF_WAVE_MAX_VALUE, + CONF_WAVEFORM_SEND_LAST_VALUE, + CONF_WAVE_MAX_LENGTH, +) + + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSensor = nextion_ns.class_("NextionSensor", sensor.Sensor, cg.PollingComponent) + + +def CheckWaveID(value): + value = cv.int_(value) + if value < 0 or value > 3: + raise cv.Invalid(f"Valid range for {CONF_WAVE_CHANNEL_ID} is 0-3") + return value + + +def _validate(config): + if CONF_WAVE_CHANNEL_ID in config and CONF_COMPONENT_ID not in config: + raise cv.Invalid( + f"{CONF_COMPONENT_ID} is required when {CONF_WAVE_CHANNEL_ID} is set" + ) + + return config + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 2, DEVICE_CLASS_EMPTY) + .extend( + { + cv.GenerateID(): cv.declare_id(NextionSensor), + cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8), + cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID, + cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, + cv.Optional(CONF_WAVE_MAX_LENGTH, default=255): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVE_MAX_VALUE, default=100): cv.int_range( + min=1, max=1024 + ), + cv.Optional(CONF_WAVEFORM_SEND_LAST_VALUE, default=True): cv.boolean, + } + ) + .extend(CONFIG_SENSOR_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_ID, CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), + _validate, +) + + +async def to_code(config): + + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await sensor.register_sensor(var, config) + + cg.add(hub.register_sensor_component(var)) + + await setup_component_core_(var, config, ".val") + + if CONF_PRECISION in config: + cg.add(var.set_precision(config[CONF_PRECISION])) + + if CONF_COMPONENT_ID in config: + cg.add(var.set_component_id(config[CONF_COMPONENT_ID])) + + if CONF_WAVE_CHANNEL_ID in config: + cg.add(var.set_wave_channel_id(config[CONF_WAVE_CHANNEL_ID])) + + if CONF_WAVEFORM_SEND_LAST_VALUE in config: + cg.add(var.set_waveform_send_last_value(config[CONF_WAVEFORM_SEND_LAST_VALUE])) + + if CONF_WAVE_MAX_VALUE in config: + cg.add(var.set_wave_max_value(config[CONF_WAVE_MAX_VALUE])) + + if CONF_WAVE_MAX_LENGTH in config: + cg.add(var.set_wave_max_length(config[CONF_WAVE_MAX_LENGTH])) diff --git a/esphome/components/nextion/sensor/nextion_sensor.cpp b/esphome/components/nextion/sensor/nextion_sensor.cpp new file mode 100644 index 0000000000..bbcb465d85 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.cpp @@ -0,0 +1,110 @@ +#include "nextion_sensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_sensor"; + +void NextionSensor::process_sensor(const std::string &variable_name, int state) { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX && this->variable_name_ == variable_name) { + this->publish_state(state); + ESP_LOGD(TAG, "Processed sensor \"%s\" state %d", variable_name.c_str(), state); + } +} + +void NextionSensor::add_to_wave_buffer(float state) { + this->needs_to_send_update_ = true; + + int wave_state = (int) ((state / (float) this->wave_maxvalue_) * 100); + + wave_buffer_.push_back(wave_state); + + if (this->wave_buffer_.size() > this->wave_max_length_) { + this->wave_buffer_.erase(this->wave_buffer_.begin()); + } +} + +void NextionSensor::update() { + if (!this->nextion_->is_setup()) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + this->nextion_->add_to_get_queue(this); + } else { + if (this->send_last_value_) { + this->add_to_wave_buffer(this->last_value_); + } + + this->wave_update_(); + } +} + +void NextionSensor::set_state(float state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (isnan(state)) + return; + + if (this->wave_chan_id_ == UINT8_MAX) { + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + + if (this->precision_ > 0) { + double to_multiply = pow(10, this->precision_); + int state_value = (int) (state * to_multiply); + + this->nextion_->add_no_result_to_queue_with_set(this, (int) state_value); + } else { + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + } + } else { + if (this->send_last_value_) { + this->last_value_ = state; // Update will handle setting the buffer + } else { + this->add_to_wave_buffer(state); + } + } + + if (this->wave_chan_id_ == UINT8_MAX) { + if (publish) { + this->publish_state(state); + } else { + this->raw_state = state; + this->state = state; + this->has_state_ = true; + } + } + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for sensor \"%s\" state %lf", this->variable_name_.c_str(), state); +} + +void NextionSensor::wave_update_() { + if (this->nextion_->is_sleeping() || this->wave_buffer_.empty()) { + return; + } + +#ifdef NEXTION_PROTOCOL_LOG + size_t buffer_to_send = + this->wave_buffer_.size() < 255 ? this->wave_buffer_.size() : 255; // ADDT command can only send 255 + + ESP_LOGN(TAG, "wave_update send %zu of %zu value(s) to wave nextion component id %d and wave channel id %d", + buffer_to_send, this->wave_buffer_.size(), this->component_id_, this->wave_chan_id_); +#endif + + this->nextion_->add_addt_command_to_queue(this); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/sensor/nextion_sensor.h b/esphome/components/nextion/sensor/nextion_sensor.h new file mode 100644 index 0000000000..e4dde9a513 --- /dev/null +++ b/esphome/components/nextion/sensor/nextion_sensor.h @@ -0,0 +1,49 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSensor; + +class NextionSensor : public NextionComponent, public sensor::Sensor, public PollingComponent { + public: + NextionSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + + void update_component() override { this->update(); } + void update() override; + void add_to_wave_buffer(float state); + void set_precision(uint8_t precision) { this->precision_ = precision; } + void set_component_id(uint8_t component_id) { component_id_ = component_id; } + void set_wave_channel_id(uint8_t wave_chan_id) { this->wave_chan_id_ = wave_chan_id; } + void set_wave_max_value(uint32_t wave_maxvalue) { this->wave_maxvalue_ = wave_maxvalue; } + void process_sensor(const std::string &variable_name, int state) override; + + void set_state(float state) override { this->set_state(state, true, true); } + void set_state(float state, bool publish) override { this->set_state(state, publish, true); } + void set_state(float state, bool publish, bool send_to_nextion) override; + + void set_waveform_send_last_value(bool send_last_value) { this->send_last_value_ = send_last_value; } + uint8_t get_wave_chan_id() { return this->wave_chan_id_; } + void set_wave_max_length(int wave_max_length) { this->wave_max_length_ = wave_max_length; } + NextionQueueType get_queue_type() override { + return this->wave_chan_id_ == UINT8_MAX ? NextionQueueType::SENSOR : NextionQueueType::WAVEFORM_SENSOR; + } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } + + protected: + uint8_t precision_ = 0; + uint32_t wave_maxvalue_ = 255; + + float last_value_ = 0; + bool send_last_value_ = true; + void wave_update_(); +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/__init__.py b/esphome/components/nextion/switch/__init__.py new file mode 100644 index 0000000000..068681fa14 --- /dev/null +++ b/esphome/components/nextion/switch/__init__.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch + +from esphome.const import CONF_ID +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONF_COMPONENT_NAME, + CONF_VARIABLE_NAME, + CONFIG_SWITCH_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionSwitch = nextion_ns.class_("NextionSwitch", switch.Switch, cg.PollingComponent) + +CONFIG_SCHEMA = cv.All( + switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionSwitch), + } + ) + .extend(CONFIG_SWITCH_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")), + cv.has_exactly_one_key(CONF_COMPONENT_NAME, CONF_VARIABLE_NAME), +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await switch.register_switch(var, config) + + cg.add(hub.register_switch_component(var)) + + await setup_component_core_(var, config, ".val") diff --git a/esphome/components/nextion/switch/nextion_switch.cpp b/esphome/components/nextion/switch/nextion_switch.cpp new file mode 100644 index 0000000000..1f32ad3425 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.cpp @@ -0,0 +1,52 @@ +#include "nextion_switch.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { + +static const char *const TAG = "nextion_switch"; + +void NextionSwitch::process_bool(const std::string &variable_name, bool on) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(on); + + ESP_LOGD(TAG, "Processed switch \"%s\" state %s", variable_name.c_str(), state ? "ON" : "OFF"); + } +} + +void NextionSwitch::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionSwitch::set_state(bool state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->needs_to_send_update_ = false; + this->nextion_->add_no_result_to_queue_with_set(this, (int) state); + } + } + if (publish) { + this->publish_state(state); + } else { + this->state = state; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Updated switch \"%s\" state %s", this->variable_name_.c_str(), ONOFF(state)); +} + +void NextionSwitch::write_state(bool state) { this->set_state(state); } + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/switch/nextion_switch.h b/esphome/components/nextion/switch/nextion_switch.h new file mode 100644 index 0000000000..1548287473 --- /dev/null +++ b/esphome/components/nextion/switch/nextion_switch.h @@ -0,0 +1,34 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionSwitch; + +class NextionSwitch : public NextionComponent, public switch_::Switch, public PollingComponent { + public: + NextionSwitch(NextionBase *nextion) { this->nextion_ = nextion; } + + void update() override; + void update_component() override { this->update(); } + void process_bool(const std::string &variable_name, bool on) override; + + void set_state(bool state) override { this->set_state(state, true, true); } + void set_state(bool state, bool publish) override { this->set_state(state, publish, true); } + void set_state(bool state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::SWITCH; } + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value != 0, publish, send_to_nextion); + } + + protected: + void write_state(bool state) override; +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py new file mode 100644 index 0000000000..9c170dd807 --- /dev/null +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -0,0 +1,38 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID + +from .. import nextion_ns, CONF_NEXTION_ID + +from ..base_component import ( + setup_component_core_, + CONFIG_TEXT_COMPONENT_SCHEMA, +) + +CODEOWNERS = ["@senexcrenshaw"] + +NextionTextSensor = nextion_ns.class_( + "NextionTextSensor", text_sensor.TextSensor, cg.PollingComponent +) + +CONFIG_SCHEMA = ( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(NextionTextSensor), + } + ) + .extend(CONFIG_TEXT_COMPONENT_SCHEMA) + .extend(cv.polling_component_schema("never")) +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_NEXTION_ID]) + var = cg.new_Pvariable(config[CONF_ID], hub) + await cg.register_component(var, config) + await text_sensor.register_text_sensor(var, config) + + cg.add(hub.register_textsensor_component(var)) + + await setup_component_core_(var, config, ".txt") diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.cpp b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp new file mode 100644 index 0000000000..08f032df74 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.cpp @@ -0,0 +1,49 @@ +#include "nextion_textsensor.h" +#include "esphome/core/util.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace nextion { +static const char *const TAG = "nextion_textsensor"; + +void NextionTextSensor::process_text(const std::string &variable_name, const std::string &text_value) { + if (!this->nextion_->is_setup()) + return; + if (this->variable_name_ == variable_name) { + this->publish_state(text_value); + ESP_LOGD(TAG, "Processed text_sensor \"%s\" state \"%s\"", variable_name.c_str(), text_value.c_str()); + } +} + +void NextionTextSensor::update() { + if (!this->nextion_->is_setup()) + return; + this->nextion_->add_to_get_queue(this); +} + +void NextionTextSensor::set_state(const std::string &state, bool publish, bool send_to_nextion) { + if (!this->nextion_->is_setup()) + return; + + if (send_to_nextion) { + if (this->nextion_->is_sleeping() || !this->visible_) { + this->needs_to_send_update_ = true; + } else { + this->nextion_->add_no_result_to_queue_with_set(this, state); + } + } + + if (publish) { + this->publish_state(state); + } else { + this->state = state; + this->has_state_ = true; + } + + this->update_component_settings(); + + ESP_LOGN(TAG, "Wrote state for text_sensor \"%s\" state \"%s\"", this->variable_name_.c_str(), state.c_str()); +} + +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/nextion/text_sensor/nextion_textsensor.h b/esphome/components/nextion/text_sensor/nextion_textsensor.h new file mode 100644 index 0000000000..5716d0a008 --- /dev/null +++ b/esphome/components/nextion/text_sensor/nextion_textsensor.h @@ -0,0 +1,32 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "../nextion_component.h" +#include "../nextion_base.h" + +namespace esphome { +namespace nextion { +class NextionTextSensor; + +class NextionTextSensor : public NextionComponent, public text_sensor::TextSensor, public PollingComponent { + public: + NextionTextSensor(NextionBase *nextion) { this->nextion_ = nextion; } + void update() override; + void update_component() override { this->update(); } + void on_state_changed(const std::string &state); + + void process_text(const std::string &variable_name, const std::string &text_value) override; + + void set_state(const std::string &state, bool publish) override { this->set_state(state, publish, true); } + void set_state(const std::string &state) override { this->set_state(state, true, true); } + void set_state(const std::string &state, bool publish, bool send_to_nextion) override; + + void send_state_to_nextion() override { this->set_state(this->state, false, true); }; + NextionQueueType get_queue_type() override { return NextionQueueType::TEXT_SENSOR; } + void set_state_from_int(int state_value, bool publish, bool send_to_nextion) override {} + void set_state_from_string(const std::string &state_value, bool publish, bool send_to_nextion) override { + this->set_state(state_value, publish, send_to_nextion); + } +}; +} // namespace nextion +} // namespace esphome diff --git a/esphome/components/uart/uart.h b/esphome/components/uart/uart.h index 6dd62070da..cd54290c73 100644 --- a/esphome/components/uart/uart.h +++ b/esphome/components/uart/uart.h @@ -56,6 +56,7 @@ class ESP8266SoftwareSerial { class UARTComponent : public Component, public Stream { public: void set_baud_rate(uint32_t baud_rate) { baud_rate_ = baud_rate; } + uint32_t get_baud_rate() const { return baud_rate_; } uint32_t get_config(); diff --git a/script/ci-custom.py b/script/ci-custom.py index 02f193c6e0..d79e5b5e2f 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from helpers import git_ls_files, filter_changed import codecs import collections import fnmatch @@ -12,7 +13,6 @@ import functools import argparse sys.path.append(os.path.dirname(__file__)) -from helpers import git_ls_files, filter_changed def find_all(a_str, sub): @@ -562,6 +562,7 @@ def lint_inclusive_language(fname, match): "esphome/components/number/number.h", "esphome/components/output/binary_output.h", "esphome/components/output/float_output.h", + "esphome/components/nextion/nextion_base.h", "esphome/components/sensor/sensor.h", "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", diff --git a/tests/test1.yaml b/tests/test1.yaml index 1c522a23d4..f9fe7d106d 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1055,10 +1055,6 @@ binary_sensor: pin: GPIO27 threshold: 1000 id: btn_left - - platform: nextion - page_id: 0 - component_id: 2 - name: 'Nextion Component 2 Touch' - platform: template name: 'Garage Door Open' id: garage_door @@ -1882,11 +1878,6 @@ display: intensity: 3 lambda: |- it.print("1234"); - - platform: nextion - uart_id: uart0 - lambda: |- - it.set_component_value("gauge", 50); - it.set_component_text("textview", "Hello World!"); - platform: pcd8544 cs_pin: GPIO23 dc_pin: GPIO23 diff --git a/tests/test3.yaml b/tests/test3.yaml index c709539309..acd975d794 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -269,6 +269,7 @@ wled: adalight: + sensor: - platform: apds9960 type: proximity @@ -534,6 +535,15 @@ sensor: export_reactive_energy: name: 'Export Reactive Energy' + - platform: nextion + id: testnumber + name: 'testnumber' + variable_name: testnumber + - platform: nextion + id: testwave + name: 'testwave' + component_id: 2 + wave_channel_id: 1 time: - platform: homeassistant @@ -605,7 +615,14 @@ binary_sensor: binary_sensors: - id: custom_binary_sensor name: Custom Binary Sensor - + - platform: nextion + page_id: 0 + component_id: 2 + name: 'Nextion Component 2 Touch' + - platform: nextion + id: r0_sensor + name: 'R0 Sensor' + component_name: page0.r0 globals: - id: my_global_string type: std::string @@ -653,6 +670,11 @@ text_sensor: text_sensors: - id: custom_text_sensor name: Custom Text Sensor + - platform: nextion + name: text0 + id: text0 + update_interval: 4s + component_name: text0 script: - id: my_script @@ -704,6 +726,10 @@ switch: switches: - id: custom_switch name: Custom Switch + - platform: nextion + id: r0 + name: 'R0 Switch' + component_name: page0.r0 custom_component: lambda: |- @@ -1086,6 +1112,16 @@ display: id: my_matrix lambda: |- it.printdigit("hello"); + - platform: nextion + uart_id: uart1 + tft_url: 'http://esphome.io/default35.tft' + update_interval: 5s + on_sleep: + then: + lambda: 'ESP_LOGD("display","Display went to sleep");' + on_wake: + then: + lambda: 'ESP_LOGD("display","Display woke up");' http_request: useragent: esphome/device From 54eb6070fb47da94e208e9daac60b64273d2fb3c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jul 2021 13:55:58 +1200 Subject: [PATCH 044/105] Bump version to v1.20.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 30c4748933..35f1e560e1 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -2,7 +2,7 @@ MAJOR_VERSION = 1 MINOR_VERSION = 20 -PATCH_VERSION = "0b1" +PATCH_VERSION = "0b2" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" From 3715ba030bfa93a396ad2e654c1009018b043eb3 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Wed, 14 Jul 2021 23:45:41 -0400 Subject: [PATCH 045/105] Fix ethernet component hostname handling (#2010) Co-authored-by: Otto Winter --- .gitignore | 3 + .../ethernet/ethernet_component.cpp | 213 +++++++++--------- .../components/ethernet/ethernet_component.h | 10 +- 3 files changed, 120 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index a24550ad54..954ecb2cb8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ __pycache__/ # Intellij Idea .idea +# Vim +*.swp + # Hide some OS X stuff .DS_Store .AppleDouble diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 22007caafa..52c184eef6 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -25,6 +25,13 @@ static const char *const TAG = "ethernet"; EthernetComponent *global_eth_component; +#define ESPHL_ERROR_CHECK(err, message) \ + if (err != ESP_OK) { \ + ESP_LOGE(TAG, message ": (%d) %s", err, esp_err_to_name(err)); \ + this->mark_failed(); \ + return; \ + } + EthernetComponent::EthernetComponent() { global_eth_component = this; } void EthernetComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Ethernet..."); @@ -36,103 +43,6 @@ void EthernetComponent::setup() { this->power_pin_->setup(); } - this->start_connect_(); - -#ifdef USE_MDNS - network_setup_mdns(); -#endif -} -void EthernetComponent::loop() { - const uint32_t now = millis(); - if (!this->connected_ && !this->last_connected_ && now - this->connect_begin_ > 15000) { - ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); - this->start_connect_(); - return; - } - - if (this->connected_ == this->last_connected_) - // nothing changed - return; - - if (this->connected_) { - // connection established - ESP_LOGI(TAG, "Connected via Ethernet!"); - this->dump_connect_params_(); - this->status_clear_warning(); - } else { - // connection lost - ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); - this->start_connect_(); - } - - this->last_connected_ = this->connected_; - - network_tick_mdns(); -} -void EthernetComponent::dump_config() { - ESP_LOGCONFIG(TAG, "Ethernet:"); - this->dump_connect_params_(); - LOG_PIN(" Power Pin: ", this->power_pin_); - ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); - ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); - ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); -} -float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } -bool EthernetComponent::can_proceed() { return this->is_connected(); } -IPAddress EthernetComponent::get_ip_address() { - tcpip_adapter_ip_info_t ip; - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); - return IPAddress(ip.ip.addr); -} - -void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { - const char *event_name; - - switch (event) { - case SYSTEM_EVENT_ETH_START: - event_name = "ETH started"; - break; - case SYSTEM_EVENT_ETH_STOP: - event_name = "ETH stopped"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_CONNECTED: - event_name = "ETH connected"; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - event_name = "ETH disconnected"; - this->connected_ = false; - break; - case SYSTEM_EVENT_ETH_GOT_IP: - event_name = "ETH Got IP"; - this->connected_ = true; - break; - default: - return; - } - - ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); -} - -#define ESPHL_ERROR_CHECK(err, message) \ - if (err != ESP_OK) { \ - ESP_LOGE(TAG, message ": %d", err); \ - this->mark_failed(); \ - return; \ - } - -void EthernetComponent::start_connect_() { - this->connect_begin_ = millis(); - this->status_set_warning(); - - esp_err_t err; - if (this->initialized_) { - // already initialized - err = esp_eth_enable(); - ESPHL_ERROR_CHECK(err, "ETH enable error"); - return; - } - switch (this->type_) { case ETHERNET_TYPE_LAN8720: { memcpy(&this->eth_config, &phy_lan8720_default_ethernet_config, sizeof(eth_config_t)); @@ -160,16 +70,111 @@ void EthernetComponent::start_connect_() { tcpipInit(); + esp_err_t err; err = esp_eth_init(&this->eth_config); - if (err != ESP_OK) { - ESP_LOGE(TAG, "ETH init error: %d", err); - this->mark_failed(); - return; + ESPHL_ERROR_CHECK(err, "ETH init error"); + err = esp_eth_enable(); + ESPHL_ERROR_CHECK(err, "ETH enable error"); + +#ifdef USE_MDNS + network_setup_mdns(); +#endif +} +void EthernetComponent::loop() { + const uint32_t now = millis(); + + switch (this->state_) { + case EthernetComponentState::STOPPED: + if (this->started_) { + ESP_LOGI(TAG, "Starting ethernet connection"); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTING: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (this->connected_) { + // connection established + ESP_LOGI(TAG, "Connected via Ethernet!"); + this->state_ = EthernetComponentState::CONNECTED; + + this->dump_connect_params_(); + this->status_clear_warning(); + + network_tick_mdns(); + } else if (now - this->connect_begin_ > 15000) { + ESP_LOGW(TAG, "Connecting via ethernet failed! Re-connecting..."); + this->start_connect_(); + } + break; + case EthernetComponentState::CONNECTED: + if (!this->started_) { + ESP_LOGI(TAG, "Stopped ethernet connection"); + this->state_ = EthernetComponentState::STOPPED; + } else if (!this->connected_) { + ESP_LOGW(TAG, "Connection via Ethernet lost! Re-connecting..."); + this->state_ = EthernetComponentState::CONNECTING; + this->start_connect_(); + } + break; + } +} +void EthernetComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Ethernet:"); + this->dump_connect_params_(); + LOG_PIN(" Power Pin: ", this->power_pin_); + ESP_LOGCONFIG(TAG, " MDC Pin: %u", this->mdc_pin_); + ESP_LOGCONFIG(TAG, " MDIO Pin: %u", this->mdio_pin_); + ESP_LOGCONFIG(TAG, " Type: %s", this->type_ == ETHERNET_TYPE_LAN8720 ? "LAN8720" : "TLK110"); +} +float EthernetComponent::get_setup_priority() const { return setup_priority::WIFI; } +bool EthernetComponent::can_proceed() { return this->is_connected(); } +IPAddress EthernetComponent::get_ip_address() { + tcpip_adapter_ip_info_t ip; + tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); + return IPAddress(ip.ip.addr); +} + +void EthernetComponent::on_wifi_event_(system_event_id_t event, system_event_info_t info) { + const char *event_name; + + switch (event) { + case SYSTEM_EVENT_ETH_START: + event_name = "ETH started"; + this->started_ = true; + break; + case SYSTEM_EVENT_ETH_STOP: + event_name = "ETH stopped"; + this->started_ = false; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_CONNECTED: + event_name = "ETH connected"; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + event_name = "ETH disconnected"; + this->connected_ = false; + break; + case SYSTEM_EVENT_ETH_GOT_IP: + event_name = "ETH Got IP"; + this->connected_ = true; + break; + default: + return; } - this->initialized_ = true; + ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); +} - tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); +void EthernetComponent::start_connect_() { + this->connect_begin_ = millis(); + this->status_set_warning(); + + esp_err_t err; + err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str()); + ESPHL_ERROR_CHECK(err, "ETH set hostname error"); tcpip_adapter_ip_info_t info; if (this->manual_ip_.has_value()) { @@ -220,7 +225,7 @@ void EthernetComponent::eth_phy_power_enable_(bool enable) { delay(1); global_eth_component->orig_power_enable_fun_(enable); } -bool EthernetComponent::is_connected() { return this->connected_ && this->last_connected_; } +bool EthernetComponent::is_connected() { return this->state_ == EthernetComponentState::CONNECTED; } void EthernetComponent::dump_connect_params_() { tcpip_adapter_ip_info_t ip; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_ETH, &ip); diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 2dbd8ccd9d..326cd1edea 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -26,6 +26,12 @@ struct ManualIP { IPAddress dns2; ///< The second DNS server. 0.0.0.0 for default. }; +enum class EthernetComponentState { + STOPPED, + CONNECTING, + CONNECTED, +}; + class EthernetComponent : public Component { public: EthernetComponent(); @@ -65,9 +71,9 @@ class EthernetComponent : public Component { eth_clock_mode_t clk_mode_{ETH_CLOCK_GPIO0_IN}; optional manual_ip_{}; - bool initialized_{false}; + bool started_{false}; bool connected_{false}; - bool last_connected_{false}; + EthernetComponentState state_{EthernetComponentState::STOPPED}; uint32_t connect_begin_; eth_config_t eth_config; eth_phy_power_enable_func orig_power_enable_fun_; From 4f9a56c8841a247f7d82fcd0cbe40902a77ba9e2 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:30:04 +0200 Subject: [PATCH 046/105] Refactor docker build system and workflows (#2023) --- .github/workflows/ci-docker.yml | 47 ++-- .github/workflows/ci.yml | 178 ++++++------- .github/workflows/docker-lint-build.yml | 108 ++++++-- .github/workflows/matchers/pytest.json | 19 ++ .github/workflows/release-dev.yml | 247 ------------------- .github/workflows/release.yml | 315 ++++++------------------ docker/Dockerfile | 2 +- docker/Dockerfile.hassio | 2 +- docker/Dockerfile.lint | 3 +- docker/build.py | 177 +++++++++++++ esphome/const.py | 6 +- script/bump-docker-base-version.py | 50 ---- script/bump-version.py | 10 +- 13 files changed, 456 insertions(+), 708 deletions(-) create mode 100644 .github/workflows/matchers/pytest.json delete mode 100644 .github/workflows/release-dev.yml create mode 100755 docker/build.py delete mode 100755 script/bump-docker-base-version.py diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml index 91ec88aeb3..45fd3e141b 100644 --- a/.github/workflows/ci-docker.yml +++ b/.github/workflows/ci-docker.yml @@ -18,38 +18,23 @@ jobs: name: Build docker containers runs-on: ubuntu-latest strategy: - fail-fast: false matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker", "lint"] steps: - - uses: actions/checkout@v2 - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=check" >> $GITHUB_ENV - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=ci" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121b3f1339..de777d5a3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,40 +4,36 @@ name: CI on: push: - # On dev branch release-dev already performs CI checks - # On other branches the `pull_request` trigger will be used - branches: [beta, release] + branches: [dev, beta, release] pull_request: jobs: - lint-clang-format: + ci-with-container: + name: ${{ matrix.name }} runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files strategy: fail-fast: false matrix: - split: [1, 2, 3, 4] + include: + - id: clang-format + name: Run script/clang-format + - id: clang-tidy + name: Run script/clang-tidy 1/4 + split: 1 + - id: clang-tidy + name: Run script/clang-tidy 2/4 + split: 2 + - id: clang-tidy + name: Run script/clang-tidy 3/4 + split: 3 + - id: clang-tidy + name: Run script/clang-tidy 4/4 + split: 4 + + # cpp lint job runs with esphome-lint docker image so that clang-format-* + # doesn't have to be installed + container: esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled @@ -45,26 +41,57 @@ jobs: - name: Set up platformio environment run: pio init --ide atom - - name: Register problem matchers run: | echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" echo "::add-matcher::.github/workflows/matchers/gcc.json" + + - name: Run clang-format + run: script/clang-format -i + if: ${{ matrix.id == 'clang-format' }} + - name: Run clang-tidy run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} + if: ${{ matrix.id == 'clang-tidy' }} + - name: Suggest changes run: script/ci-suggest-changes - lint-python: + ci: # Don't use the esphome-lint docker image because it may contain outdated requirements. # This way, all dependencies are cached via the cache action. + name: ${{ matrix.name }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - id: ci-custom + name: Run script/ci-custom + - id: lint-python + name: Run script/lint-python + - id: test + file: tests/test1.yaml + name: Test tests/test1.yaml + - id: test + file: tests/test2.yaml + name: Test tests/test2.yaml + - id: test + file: tests/test3.yaml + name: Test tests/test3.yaml + - id: test + file: tests/test4.yaml + name: Test tests/test4.yaml + - id: pytest + name: Run pytest + steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.7' + - name: Cache pip modules uses: actions/cache@v1 with: @@ -72,6 +99,17 @@ jobs: key: esphome-pip-3.7-${{ hashFiles('setup.py') }} restore-keys: | esphome-pip-3.7- + + # Use per test platformio cache because tests have different platform versions + - name: Cache ~/.platformio + uses: actions/cache@v1 + with: + path: ~/.platformio + key: test-home-platformio-${{ matrix.file }}-${{ hashFiles('esphome/core/config.py') }} + restore-keys: | + test-home-platformio-${{ matrix.file }}- + if: ${{ matrix.id == 'test' }} + - name: Set up python environment run: script/setup @@ -80,82 +118,22 @@ jobs: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" echo "::add-matcher::.github/workflows/matchers/lint-python.json" echo "::add-matcher::.github/workflows/matchers/python.json" + echo "::add-matcher::.github/workflows/matchers/pytest.json" + echo "::add-matcher::.github/workflows/matchers/gcc.json" + - name: Lint Custom - run: script/ci-custom.py + run: | + script/ci-custom.py + script/build_codeowners.py --check + if: ${{ matrix.id == 'ci-custom' }} - name: Lint Python run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check + if: ${{ matrix.id == 'lint-python' }} - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup + - run: esphome compile ${{ matrix.file }} + if: ${{ matrix.id == 'test' }} - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - name: Run pytest run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + pytest -vv --tb=native tests + if: ${{ matrix.id == 'pytest' }} diff --git a/.github/workflows/docker-lint-build.yml b/.github/workflows/docker-lint-build.yml index d254ac332a..32aec87cdd 100644 --- a/.github/workflows/docker-lint-build.yml +++ b/.github/workflows/docker-lint-build.yml @@ -13,30 +13,88 @@ on: - '.github/workflows/docker-lint-build.yml' jobs: - publish-docker-lint-iage: - name: Build docker containers + deploy-docker: + name: Build and publish docker containers + if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, armv7, aarch64] + build_type: ["lint"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - echo "TAG=1.1" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "esphome/esphome-lint:latest" || true - - name: Build - run: | - docker build \ - --cache-from "esphome/esphome-lint:latest" \ - --file "docker/Dockerfile.lint" \ - --tag "esphome/esphome-lint:latest" \ - --tag "esphome/esphome-lint:${TAG}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "esphome/esphome-lint:${TAG}" - docker push "esphome/esphome-lint:latest" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + + - name: Run build + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run push + run: | + docker/build.py \ + --tag "${TAG}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push + + deploy-docker-manifest: + if: github.repository == 'esphome/esphome' + runs-on: ubuntu-latest + needs: [deploy-docker] + strategy: + matrix: + build_type: ["lint"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + - name: Set TAG + run: | + echo "TAG=1.1" >> $GITHUB_ENV + - name: Enable experimental manifest support + run: | + mkdir -p ~/.docker + echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json + + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run manifest + run: | + docker/build.py \ + --tag "${TAG}" \ + --build-type "${{ matrix.build_type }}" \ + manifest diff --git a/.github/workflows/matchers/pytest.json b/.github/workflows/matchers/pytest.json new file mode 100644 index 0000000000..0eb8f050e6 --- /dev/null +++ b/.github/workflows/matchers/pytest.json @@ -0,0 +1,19 @@ +{ + "problemMatcher": [ + { + "owner": "pytest", + "fileLocation": "absolute", + "pattern": [ + { + "regexp": "^\\s+File \"(.*)\", line (\\d+), in (.*)$", + "file": 1, + "line": 2 + }, + { + "regexp": "^\\s+(.*)$", + "message": 1 + } + ] + } + ] +} diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml deleted file mode 100644 index f8b90d524f..0000000000 --- a/.github/workflows/release-dev.yml +++ /dev/null @@ -1,247 +0,0 @@ -name: Publish dev releases to docker hub - -on: - push: - branches: - - dev - -jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests - - deploy-docker: - name: Build and publish docker containers - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] - strategy: - matrix: - arch: [amd64, armv7, aarch64] - # Hassio dev image doesn't use esphome/esphome-hassio-$arch and uses base directly - build_type: ["docker"] - steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" - - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi - - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:dev" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --tag "${BUILD_TO}:dev" \ - --cache-from "${BUILD_TO}:dev" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: | - docker push "${BUILD_TO}:${TAG}" - docker push "${BUILD_TO}:dev" - - - deploy-docker-manifest: - if: github.repository == 'esphome/esphome' - runs-on: ubuntu-latest - needs: [deploy-docker] - steps: - - name: Enable experimental manifest support - run: | - mkdir -p ~/.docker - echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_SHA:0:7}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} - - docker manifest create esphome/esphome:dev \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 383f2878e5..1b15b540f6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,164 +1,35 @@ name: Publish Release on: + workflow_dispatch: release: types: [published] + schedule: + - cron: "0 2 * * *" jobs: - # THE LINT/TEST JOBS ARE COPIED FROM ci.yaml - - lint-clang-format: + init: + name: Initialize build runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 + outputs: + tag: ${{ steps.tag.outputs.tag }} steps: - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - name: Run clang-format - run: script/clang-format -i - - name: Suggest changes - run: script/ci-suggest-changes - - lint-clang-tidy: - runs-on: ubuntu-latest - # cpp lint job runs with esphome-lint docker image so that clang-format-* - # doesn't have to be installed - container: esphome/esphome-lint:1.1 - # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files - strategy: - fail-fast: false - matrix: - split: [1, 2, 3, 4] - steps: - - uses: actions/checkout@v2 - # Set up the pio project so that the cpp checks know how files are compiled - # (build flags, libraries etc) - - name: Set up platformio environment - run: pio init --ide atom - - - - name: Register problem matchers + - name: Get tag + id: tag run: | - echo "::add-matcher::.github/workflows/matchers/clang-tidy.json" - echo "::add-matcher::.github/workflows/matchers/gcc.json" - - name: Run clang-tidy - run: script/clang-tidy --all-headers --fix --split-num 4 --split-at ${{ matrix.split }} - - name: Suggest changes - run: script/ci-suggest-changes - - lint-python: - # Don't use the esphome-lint docker image because it may contain outdated requirements. - # This way, all dependencies are cached via the cache action. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up python environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - echo "::add-matcher::.github/workflows/matchers/lint-python.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Lint Custom - run: script/ci-custom.py - - name: Lint Python - run: script/lint-python - - name: Lint CODEOWNERS - run: script/build_codeowners.py --check - - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - test: - - test1 - - test2 - - test3 - - test4 - - test5 - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - # Use per test platformio cache because tests have different platform versions - - name: Cache ~/.platformio - uses: actions/cache@v1 - with: - path: ~/.platformio - key: test-home-platformio-${{ matrix.test }}-${{ hashFiles('esphome/core/config.py') }} - restore-keys: | - test-home-platformio-${{ matrix.test }}- - - name: Set up environment - run: script/setup - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/gcc.json" - echo "::add-matcher::.github/workflows/matchers/python.json" - - run: esphome compile tests/${{ matrix.test }}.yaml - - pytest: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.7' - - name: Cache pip modules - uses: actions/cache@v1 - with: - path: ~/.cache/pip - key: esphome-pip-3.7-${{ hashFiles('setup.py') }} - restore-keys: | - esphome-pip-3.7- - - name: Set up environment - run: script/setup - - name: Install Github Actions annotator - run: pip install pytest-github-actions-annotate-failures - - - name: Register problem matchers - run: | - echo "::add-matcher::.github/workflows/matchers/python.json" - - name: Run pytest - run: | - pytest \ - -qq \ - --durations=10 \ - -o console_output_style=count \ - tests + if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + else + TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") + today="$(date --utc '+%Y%m%d')" + TAG="${TAG}${today}" + fi + echo "::set-output name=tag::${TAG}" deploy-pypi: name: Build and publish to PyPi - if: github.repository == 'esphome/esphome' - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -182,119 +53,85 @@ jobs: name: Build and publish docker containers if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [lint-clang-format, lint-clang-tidy, lint-python, test, pytest] + needs: [init] strategy: matrix: arch: [amd64, armv7, aarch64] - build_type: ["hassio", "docker"] + build_type: ["ha-addon", "docker"] steps: - - uses: actions/checkout@v2 - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV - - name: Set up env variables - run: | - base_version="3.4.0" + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - if [[ "${{ matrix.build_type }}" == "hassio" ]]; then - build_from="esphome/esphome-hassio-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-hassio-${{ matrix.arch }}" - dockerfile="docker/Dockerfile.hassio" - else - build_from="esphome/esphome-base-${{ matrix.arch }}:${base_version}" - build_to="esphome/esphome-${{ matrix.arch }}" - dockerfile="docker/Dockerfile" - fi + - name: Run build + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + build - if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then - cache_tag="beta" - else - cache_tag="latest" - fi + - name: Log in to docker hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - # Set env variables so these values don't need to be calculated again - echo "BUILD_FROM=${build_from}" >> $GITHUB_ENV - echo "BUILD_TO=${build_to}" >> $GITHUB_ENV - echo "DOCKERFILE=${dockerfile}" >> $GITHUB_ENV - echo "CACHE_TAG=${cache_tag}" >> $GITHUB_ENV - - name: Pull for cache - run: | - docker pull "${BUILD_TO}:${CACHE_TAG}" || true - - name: Register QEMU binfmt - run: docker run --rm --privileged multiarch/qemu-user-static:5.2.0-2 --reset -p yes - - run: | - docker build \ - --build-arg "BUILD_FROM=${BUILD_FROM}" \ - --build-arg "BUILD_VERSION=${TAG}" \ - --tag "${BUILD_TO}:${TAG}" \ - --cache-from "${BUILD_TO}:${CACHE_TAG}" \ - --file "${DOCKERFILE}" \ - . - - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - run: docker push "${BUILD_TO}:${TAG}" - - # Always publish to beta tag (also full releases) - - name: Publish docker beta tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:beta" - docker push "${BUILD_TO}:beta" - - - if: ${{ !github.event.release.prerelease }} - name: Publish docker latest tag - run: | - docker tag "${BUILD_TO}:${TAG}" "${BUILD_TO}:latest" - docker push "${BUILD_TO}:latest" + - name: Run push + run: | + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --arch "${{ matrix.arch }}" \ + --build-type "${{ matrix.build_type }}" \ + push deploy-docker-manifest: if: github.repository == 'esphome/esphome' runs-on: ubuntu-latest - needs: [deploy-docker] + needs: [init, deploy-docker] + strategy: + matrix: + build_type: ["ha-addon", "docker"] steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' - name: Enable experimental manifest support run: | mkdir -p ~/.docker echo "{\"experimental\": \"enabled\"}" > ~/.docker/config.json - - name: Set TAG - run: | - TAG="${GITHUB_REF#refs/tags/v}" - echo "TAG=${TAG}" >> $GITHUB_ENV + - name: Log in to docker hub - env: - DOCKER_USER: ${{ secrets.DOCKER_USER }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - run: docker login -u "${DOCKER_USER}" -p "${DOCKER_PASSWORD}" - - name: "Create the manifest" - run: | - docker manifest create esphome/esphome:${TAG} \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:${TAG} + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Log in to the GitHub container registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Publish docker beta tag + - name: Run manifest run: | - docker manifest create esphome/esphome:beta \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:beta - - - name: Publish docker latest tag - if: ${{ !github.event.release.prerelease }} - run: | - docker manifest create esphome/esphome:latest \ - esphome/esphome-aarch64:${TAG} \ - esphome/esphome-amd64:${TAG} \ - esphome/esphome-armv7:${TAG} - docker manifest push esphome/esphome:latest + docker/build.py \ + --tag "${{ needs.init.outputs.tag }}" \ + --build-type "${{ matrix.build_type }}" \ + manifest deploy-hassio-repo: - if: github.repository == 'esphome/esphome' + if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest needs: [deploy-docker] steps: diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d126a2944..907c041119 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:3.4.0 +ARG BUILD_FROM=esphome/esphome-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index 5dd9339b18..ad80074ada 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -1,4 +1,4 @@ -ARG BUILD_FROM +ARG BUILD_FROM=esphome/esphome-hassio-base:latest FROM ${BUILD_FROM} # First install requirements to leverage caching when requirements don't change diff --git a/docker/Dockerfile.lint b/docker/Dockerfile.lint index 60d63152a0..3a090c3b41 100644 --- a/docker/Dockerfile.lint +++ b/docker/Dockerfile.lint @@ -1,4 +1,5 @@ -FROM esphome/esphome-lint-base:3.4.0 +ARG BUILD_FROM=esphome/esphome-lint-base:latest +FROM ${BUILD_FROM} COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / RUN \ diff --git a/docker/build.py b/docker/build.py new file mode 100755 index 0000000000..97222df8d1 --- /dev/null +++ b/docker/build.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +from dataclasses import dataclass +import subprocess +import argparse +import platform +import shlex +import re +import sys + + +CHANNEL_DEV = 'dev' +CHANNEL_BETA = 'beta' +CHANNEL_RELEASE = 'release' +CHANNELS = [CHANNEL_DEV, CHANNEL_BETA, CHANNEL_RELEASE] + +ARCH_AMD64 = 'amd64' +ARCH_ARMV7 = 'armv7' +ARCH_AARCH64 = 'aarch64' +ARCHS = [ARCH_AMD64, ARCH_ARMV7, ARCH_AARCH64] + +TYPE_DOCKER = 'docker' +TYPE_HA_ADDON = 'ha-addon' +TYPE_LINT = 'lint' +TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] + + +BASE_VERSION = "3.6.0" + + +parser = argparse.ArgumentParser() +parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") +parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") +parser.add_argument("--build-type", choices=TYPES, required=True, help="The type of build to run") +parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") +subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) +build_parser = subparsers.add_parser("build", help="Build the image") +push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") +manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") + + + +# only lists some possibilities, doesn't have to be perfect +# https://stackoverflow.com/a/45125525 +UNAME_TO_ARCH = { + "x86_64": ARCH_AMD64, + "aarch64": ARCH_AARCH64, + "aarch64_be": ARCH_AARCH64, + "arm": ARCH_ARMV7, +} + + +@dataclass(frozen=True) +class DockerParams: + build_from: str + build_to: str + manifest_to: str + dockerfile: str + + @classmethod + def for_type_arch(cls, build_type, arch): + prefix = { + TYPE_DOCKER: "esphome/esphome", + TYPE_HA_ADDON: "esphome/esphome-hassio", + TYPE_LINT: "esphome/esphome-lint" + }[build_type] + build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" + build_to = f"{prefix}-{arch}" + dockerfile = { + TYPE_DOCKER: "docker/Dockerfile", + TYPE_HA_ADDON: "docker/Dockerfile.hassio", + TYPE_LINT: "docker/Dockerfile.lint", + }[build_type] + return cls( + build_from=build_from, + build_to=build_to, + manifest_to=prefix, + dockerfile=dockerfile + ) + + +def main(): + args = parser.parse_args() + + def run_command(*cmd, ignore_error: bool = False): + print(f"$ {shlex.join(list(cmd))}") + if not args.dry_run: + rc = subprocess.call(list(cmd)) + if rc != 0 and not ignore_error: + print("Command failed") + sys.exit(1) + + # detect channel from tag + match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + if match is None: + channel = CHANNEL_DEV + elif match.group(1) is None: + channel = CHANNEL_RELEASE + else: + channel = CHANNEL_BETA + + tags_to_push = [args.tag] + if channel == CHANNEL_DEV: + tags_to_push.append("dev") + elif channel == CHANNEL_BETA: + tags_to_push.append("beta") + elif channel == CHANNEL_RELEASE: + # Additionally push to beta + tags_to_push.append("beta") + tags_to_push.append("latest") + + if args.command == "build": + # 1. pull cache image + params = DockerParams.for_type_arch(args.build_type, args.arch) + cache_tag = { + CHANNEL_DEV: "dev", + CHANNEL_BETA: "beta", + CHANNEL_RELEASE: "latest", + }[channel] + cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" + run_command("docker", "pull", cache_img, ignore_error=True) + + # 2. register QEMU binfmt (if not host arch) + is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch + if not is_native: + run_command( + "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", + "--reset", "-p", "yes" + ) + + # 3. build + run_command( + "docker", "build", + "--build-arg", f"BUILD_FROM={params.build_from}", + "--build-arg", f"BUILD_VERSION={args.tag}", + "--tag", f"{params.build_to}:{args.tag}", + "--cache-from", cache_img, + "--file", params.dockerfile, + "." + ) + elif args.command == "push": + params = DockerParams.for_type_arch(args.build_type, args.arch) + imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] + imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] + src = imgs[0] + # 1. tag images + for img in imgs[1:]: + run_command( + "docker", "tag", src, img + ) + # 2. push images + for img in imgs: + run_command( + "docker", "push", img + ) + elif args.command == "manifest": + manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to + + targets = [f"{manifest}:{tag}" for tag in tags_to_push] + targets += [f"ghcr.io/{manifest}:{tag}" for tag in tags_to_push] + # 1. Create manifests + for target in targets: + cmd = ["docker", "manifest", "create", target] + for arch in ARCHS: + src = f"{DockerParams.for_type_arch(args.build_type, arch).build_to}:{args.tag}" + if target.startswith("ghcr.io"): + src = f"ghcr.io/{src}" + cmd.append(src) + run_command(*cmd) + # 2. Push manifests + for target in targets: + run_command( + "docker", "manifest", "push", target + ) + + +if __name__ == "__main__": + main() diff --git a/esphome/const.py b/esphome/const.py index 35f1e560e1..c336f38b22 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,10 +1,6 @@ """Constants used by esphome.""" -MAJOR_VERSION = 1 -MINOR_VERSION = 20 -PATCH_VERSION = "0b2" -__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" -__version__ = f"{__short_version__}.{PATCH_VERSION}" +__version__ = "1.20.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" diff --git a/script/bump-docker-base-version.py b/script/bump-docker-base-version.py deleted file mode 100755 index f5f4399fd2..0000000000 --- a/script/bump-docker-base-version.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import re -import sys - - -def sub(path, pattern, repl, expected_count=1): - with open(path) as fh: - content = fh.read() - content, count = re.subn(pattern, repl, content, flags=re.MULTILINE) - if expected_count is not None: - assert count == expected_count, f"Pattern {pattern} replacement failed!" - with open(path, "wt") as fh: - fh.write(content) - - -def write_version(version: str): - for p in [ - ".github/workflows/ci-docker.yml", - ".github/workflows/release-dev.yml", - ".github/workflows/release.yml", - ]: - sub(p, r'base_version=".*"', f'base_version="{version}"') - - sub( - "docker/Dockerfile", - r"ARG BUILD_FROM=esphome/esphome-base-amd64:.*", - f"ARG BUILD_FROM=esphome/esphome-base-amd64:{version}", - ) - sub( - "docker/Dockerfile.lint", - r"FROM esphome/esphome-lint-base:.*", - f"FROM esphome/esphome-lint-base:{version}", - ) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("new_version", type=str) - args = parser.parse_args() - - version = args.new_version - print(f"Bumping to {version}") - write_version(version) - return 0 - - -if __name__ == "__main__": - sys.exit(main() or 0) diff --git a/script/bump-version.py b/script/bump-version.py index b7b048eb22..1f034344f9 100755 --- a/script/bump-version.py +++ b/script/bump-version.py @@ -50,16 +50,10 @@ def sub(path, pattern, repl, expected_count=1): def write_version(version: Version): - sub( - "esphome/const.py", r"^MAJOR_VERSION = \d+$", f"MAJOR_VERSION = {version.major}" - ) - sub( - "esphome/const.py", r"^MINOR_VERSION = \d+$", f"MINOR_VERSION = {version.minor}" - ) sub( "esphome/const.py", - r"^PATCH_VERSION = .*$", - f'PATCH_VERSION = "{version.full_patch}"', + r"^__version__ = .*$", + f'__version__ = "{version}"', ) From 70902029f88e2faf2a9ed41140febdede08e1331 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Thu, 15 Jul 2021 21:51:52 +0200 Subject: [PATCH 047/105] GH Actions CI use GHCR (#2027) --- .github/workflows/ci.yml | 2 +- docker/build.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de777d5a3b..4ccaaa47b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: # cpp lint job runs with esphome-lint docker image so that clang-format-* # doesn't have to be installed - container: esphome/esphome-lint:1.1 + container: ghcr.io/esphome/esphome-lint:1.1 steps: - uses: actions/checkout@v2 # Set up the pio project so that the cpp checks know how files are compiled diff --git a/docker/build.py b/docker/build.py index 97222df8d1..54a279f845 100755 --- a/docker/build.py +++ b/docker/build.py @@ -90,7 +90,7 @@ def main(): sys.exit(1) # detect channel from tag - match = re.match(r'\d+\.\d+(?:\.\d+)?(b\d+)?', args.tag) + match = re.match(r'^\d+\.\d+(?:\.\d+)?(b\d+)?$', args.tag) if match is None: channel = CHANNEL_DEV elif match.group(1) is None: From 7787fa8f29cbc4c1753c4e5c30cbf386620689d4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:22:42 +0200 Subject: [PATCH 048/105] Dashboard disable assets caching (#2025) --- esphome/dashboard/dashboard.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 00b12199c0..66f72bfc00 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -782,10 +782,9 @@ def make_app(debug=get_bool_env(ENV_DEV)): class StaticFileHandler(tornado.web.StaticFileHandler): def set_extra_headers(self, path): - if debug: - self.set_header( - "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" - ) + self.set_header( + "Cache-Control", "no-store, no-cache, must-revalidate, max-age=0" + ) app_settings = { "debug": debug, From f1e3ff2ed2c799a702e8f518d9b78fe558680283 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Fri, 16 Jul 2021 10:23:08 +0200 Subject: [PATCH 049/105] Improve external components error messages (#2026) --- .../external_components/__init__.py | 95 +++++++++++-------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 1602ac3b07..bf44dc1929 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,7 +109,15 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _handle_git_response(ret): +def _run_git_command(cmd): + try: + ret = subprocess.run(cmd, capture_output=True, check=False) + except FileNotFoundError as err: + raise cv.Invalid( + "git is not installed but required for external_components.\n" + "Please see https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for installing git" + ) from err + if ret.returncode != 0 and ret.stderr: err_str = ret.stderr.decode("utf-8") lines = [x.strip() for x in err_str.splitlines()] @@ -118,46 +126,59 @@ def _handle_git_response(ret): raise cv.Invalid(err_str) +def _process_git_config(config: dict, refresh) -> str: + key = f"{config[CONF_URL]}@{config.get(CONF_REF)}" + repo_dir = _compute_destination_path(key) + if not repo_dir.is_dir(): + _LOGGER.info("Cloning %s", key) + _LOGGER.debug("Location: %s", repo_dir) + cmd = ["git", "clone", "--depth=1"] + if CONF_REF in config: + cmd += ["--branch", config[CONF_REF]] + cmd += ["--", config[CONF_URL], str(repo_dir)] + _run_git_command(cmd) + + else: + # Check refresh needed + file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") + # On first clone, FETCH_HEAD does not exists + if not file_timestamp.exists(): + file_timestamp = Path(repo_dir / ".git" / "HEAD") + age = datetime.datetime.now() - datetime.datetime.fromtimestamp( + file_timestamp.stat().st_mtime + ) + if age.seconds > refresh.total_seconds: + _LOGGER.info("Updating %s", key) + _LOGGER.debug("Location: %s", repo_dir) + # Stash local changes (if any) + _run_git_command(["git", "stash", "push", "--include-untracked"]) + # Fetch remote ref + cmd = ["git", "fetch", "--", "origin"] + if CONF_REF in config: + cmd.append(config[CONF_REF]) + _run_git_command(cmd) + # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + + if (repo_dir / "esphome" / "components").is_dir(): + components_dir = repo_dir / "esphome" / "components" + elif (repo_dir / "components").is_dir(): + components_dir = repo_dir / "components" + else: + raise cv.Invalid( + "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder" + ) + + return components_dir + + def _process_single_config(config: dict): conf = config[CONF_SOURCE] if conf[CONF_TYPE] == TYPE_GIT: - key = f"{conf[CONF_URL]}@{conf.get(CONF_REF)}" - repo_dir = _compute_destination_path(key) - if not repo_dir.is_dir(): - cmd = ["git", "clone", "--depth=1"] - if CONF_REF in conf: - cmd += ["--branch", conf[CONF_REF]] - cmd += [conf[CONF_URL], str(repo_dir)] - ret = subprocess.run(cmd, capture_output=True, check=False) - _handle_git_response(ret) - - else: - # Check refresh needed - file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD") - # On first clone, FETCH_HEAD does not exists - if not file_timestamp.exists(): - file_timestamp = Path(repo_dir / ".git" / "HEAD") - age = datetime.datetime.now() - datetime.datetime.fromtimestamp( - file_timestamp.stat().st_mtime + with cv.prepend_path([CONF_SOURCE]): + components_dir = _process_git_config( + config[CONF_SOURCE], config[CONF_REFRESH] ) - if age.seconds > config[CONF_REFRESH].total_seconds: - _LOGGER.info("Executing git pull %s", key) - cmd = ["git", "pull"] - ret = subprocess.run( - cmd, cwd=repo_dir, capture_output=True, check=False - ) - _handle_git_response(ret) - - if (repo_dir / "esphome" / "components").is_dir(): - components_dir = repo_dir / "esphome" / "components" - elif (repo_dir / "components").is_dir(): - components_dir = repo_dir / "components" - else: - raise cv.Invalid( - "Could not find components folder for source. Please check the source contains a 'components' or 'esphome/components' folder", - [CONF_SOURCE], - ) - elif conf[CONF_TYPE] == TYPE_LOCAL: components_dir = Path(CORE.relative_config_path(conf[CONF_PATH])) else: From 46e50ba53ff99fdd83fd1997c12c8adcd9e884e9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Jul 2021 08:28:58 +1200 Subject: [PATCH 050/105] Bump version to v1.20.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index c336f38b22..65c6458dd6 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b2" +__version__ = "1.20.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 98855e4123f236530509f7f40c3854d538375e3e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 08:22:49 +1200 Subject: [PATCH 051/105] Number and Template Number updates (#2036) Co-authored-by: Otto winter --- esphome/components/api/api_connection.cpp | 8 +- esphome/components/mqtt/mqtt_number.cpp | 22 +++-- esphome/components/number/__init__.py | 27 ++++-- esphome/components/number/number.cpp | 69 ++++---------- esphome/components/number/number.h | 93 +++++++------------ .../components/template/number/__init__.py | 25 +++-- .../template/number/template_number.cpp | 33 ++++--- .../template/number/template_number.h | 19 ++-- esphome/components/web_server/web_server.cpp | 5 +- 9 files changed, 148 insertions(+), 153 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 8c76583fc7..79ffcfa69e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -570,11 +570,11 @@ bool APIConnection::send_number_info(number::Number *number) { msg.object_id = number->get_object_id(); msg.name = number->get_name(); msg.unique_id = get_default_unique_id("number", number); - msg.icon = number->get_icon(); + msg.icon = number->traits.get_icon(); - msg.min_value = number->get_min_value(); - msg.max_value = number->get_max_value(); - msg.step = number->get_step(); + msg.min_value = number->traits.get_min_value(); + msg.max_value = number->traits.get_max_value(); + msg.step = number->traits.get_step(); return this->send_list_entities_number_response(msg); } diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index bb67a225fd..0311526340 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -3,10 +3,6 @@ #ifdef USE_NUMBER -#ifdef USE_DEEP_SLEEP -#include "esphome/components/deep_sleep/deep_sleep_component.h" -#endif - namespace esphome { namespace mqtt { @@ -20,7 +16,7 @@ void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { auto val = parse_float(state); if (!val.has_value()) { - ESP_LOGE(TAG, "Can't convert '%s' to number!", state.c_str()); + ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str()); return; } auto call = this->number_->make_call(); @@ -39,8 +35,15 @@ std::string MQTTNumberComponent::component_type() const { return "number"; } std::string MQTTNumberComponent::friendly_name() const { return this->number_->get_name(); } void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) { - if (!this->number_->get_icon().empty()) - root["icon"] = this->number_->get_icon(); + const auto &traits = number_->traits; + // https://www.home-assistant.io/integrations/number.mqtt/ + if (!traits.get_icon().empty()) + root["icon"] = traits.get_icon(); + root["min_value"] = traits.get_min_value(); + root["max_value"] = traits.get_max_value(); + root["step"] = traits.get_step(); + + config.command_topic = true; } bool MQTTNumberComponent::send_initial_state() { if (this->number_->has_state()) { @@ -51,8 +54,9 @@ bool MQTTNumberComponent::send_initial_state() { } bool MQTTNumberComponent::is_internal() { return this->number_->is_internal(); } bool MQTTNumberComponent::publish_state(float value) { - int8_t accuracy = this->number_->get_accuracy_decimals(); - return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy)); + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + return this->publish(this->get_state_topic_(), buffer); } } // namespace mqtt diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py index ed33931d8b..bf95cb1b31 100644 --- a/esphome/components/number/__init__.py +++ b/esphome/components/number/__init__.py @@ -1,3 +1,4 @@ +from typing import Optional import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation @@ -66,12 +67,18 @@ NUMBER_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( ) -async def setup_number_core_(var, config): +async def setup_number_core_( + var, config, *, min_value: float, max_value: float, step: Optional[float] +): cg.add(var.set_name(config[CONF_NAME])) if CONF_INTERNAL in config: cg.add(var.set_internal(config[CONF_INTERNAL])) - cg.add(var.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_icon(config[CONF_ICON])) + cg.add(var.traits.set_min_value(min_value)) + cg.add(var.traits.set_max_value(max_value)) + if step is not None: + cg.add(var.traits.set_step(step)) for conf in config.get(CONF_ON_VALUE, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) @@ -92,16 +99,24 @@ async def setup_number_core_(var, config): await mqtt.register_mqtt_component(mqtt_, config) -async def register_number(var, config): +async def register_number( + var, config, *, min_value: float, max_value: float, step: Optional[float] = None +): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_number(var)) - await setup_number_core_(var, config) + await setup_number_core_( + var, config, min_value=min_value, max_value=max_value, step=step + ) -async def new_number(config): +async def new_number( + config, *, min_value: float, max_value: float, step: Optional[float] = None +): var = cg.new_Pvariable(config[CONF_ID]) - await register_number(var, config) + await register_number( + var, config, min_value=min_value, max_value=max_value, step=step + ) return var diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index eaee5d4e69..dbc1c88a5d 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -8,67 +8,38 @@ static const char *const TAG = "number"; void NumberCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); - if (this->value_.has_value()) { - auto value = *this->value_; - uint8_t accuracy = this->parent_->get_accuracy_decimals(); - float min_value = this->parent_->get_min_value(); - if (value < min_value) { - ESP_LOGW(TAG, " Value %s must not be less than minimum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(min_value, accuracy).c_str()); - this->value_.reset(); - return; - } - float max_value = this->parent_->get_max_value(); - if (value > max_value) { - ESP_LOGW(TAG, " Value %s must not be larger than maximum %s", value_accuracy_to_string(value, accuracy).c_str(), - value_accuracy_to_string(max_value, accuracy).c_str()); - this->value_.reset(); - return; - } - ESP_LOGD(TAG, " Value: %s", value_accuracy_to_string(*this->value_, accuracy).c_str()); - this->parent_->set(*this->value_); + if (!this->value_.has_value() || isnan(*this->value_)) { + ESP_LOGW(TAG, "No value set for NumberCall"); + return; } + + const auto &traits = this->parent_->traits; + auto value = *this->value_; + + float min_value = traits.get_min_value(); + if (value < min_value) { + ESP_LOGW(TAG, " Value %f must not be less than minimum %f", value, min_value); + return; + } + float max_value = traits.get_max_value(); + if (value > max_value) { + ESP_LOGW(TAG, " Value %f must not be greater than maximum %f", value, max_value); + return; + } + ESP_LOGD(TAG, " Value: %f", *this->value_); + this->parent_->control(*this->value_); } -NumberCall &NumberCall::set_value(float value) { - this->value_ = value; - return *this; -} - -const optional &NumberCall::get_value() const { return this->value_; } - -NumberCall Number::make_call() { return NumberCall(this); } - void Number::publish_state(float state) { this->has_state_ = true; this->state = state; - ESP_LOGD(TAG, "'%s': Sending state %.5f", this->get_name().c_str(), state); + ESP_LOGD(TAG, "'%s': Sending state %f", this->get_name().c_str(), state); this->state_callback_.call(state); } -uint32_t Number::update_interval() { return 0; } -Number::Number(const std::string &name) : Nameable(name), state(NAN) {} -Number::Number() : Number("") {} - void Number::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } -void Number::set_icon(const std::string &icon) { this->icon_ = icon; } -std::string Number::get_icon() { return *this->icon_; } -int8_t Number::get_accuracy_decimals() { - // use printf %g to find number of digits based on step - char buf[32]; - sprintf(buf, "%.5g", this->step_); - std::string str{buf}; - size_t dot_pos = str.find('.'); - if (dot_pos == std::string::npos) - return 0; - - return str.length() - dot_pos - 1; -} -float Number::get_state() const { return this->state; } - -bool Number::has_state() const { return this->has_state_; } uint32_t Number::hash_base() { return 2282307003UL; } diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 4fe9692a6b..e32b53187b 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -9,8 +9,8 @@ namespace number { #define LOG_NUMBER(prefix, type, obj) \ if ((obj) != nullptr) { \ ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, type, (obj)->get_name().c_str()); \ - if (!(obj)->get_icon().empty()) { \ - ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + if (!(obj)->traits.get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->traits.get_icon().c_str()); \ } \ } @@ -19,72 +19,56 @@ class Number; class NumberCall { public: explicit NumberCall(Number *parent) : parent_(parent) {} - NumberCall &set_value(float value); void perform(); - const optional &get_value() const; + NumberCall &set_value(float value) { + value_ = value; + return *this; + } + const optional &get_value() const { return value_; } protected: Number *const parent_; optional value_; }; +class NumberTraits { + public: + void set_min_value(float min_value) { min_value_ = min_value; } + float get_min_value() const { return min_value_; } + void set_max_value(float max_value) { max_value_ = max_value; } + float get_max_value() const { return max_value_; } + void set_step(float step) { step_ = step; } + float get_step() const { return step_; } + void set_icon(std::string icon) { icon_ = std::move(icon); } + const std::string &get_icon() const { return icon_; } + + protected: + float min_value_ = NAN; + float max_value_ = NAN; + float step_ = NAN; + std::string icon_; +}; + /** Base-class for all numbers. * * A number can use publish_state to send out a new value. */ class Number : public Nameable { public: - explicit Number(); - explicit Number(const std::string &name); - - /** Manually set the icon of this number. By default the number's default defined by icon() is used. - * - * @param icon The icon, for example "mdi:flash". "" to disable. - */ - void set_icon(const std::string &icon); - /// Get the Home Assistant Icon. Uses the manual override if specified or the default value instead. - std::string get_icon(); - - /// Getter-syntax for .state. - float get_state() const; - - /// Get the accuracy in decimals. Based on the step value. - int8_t get_accuracy_decimals(); - - /** Publish the current state to the front-end. - */ - void publish_state(float state); - - NumberCall make_call(); - - // ========== INTERNAL METHODS ========== - // (In most use cases you won't need these) - /// Add a callback that will be called every time the state changes. - void add_on_state_callback(std::function &&callback); - - /** This member variable stores the last state. - * - * On startup, when no state is available yet, this is NAN (not-a-number) and the validity - * can be checked using has_state(). - * - * This is exposed through a member variable for ease of use in esphome lambdas. - */ float state; + void publish_state(float state); + + NumberCall make_call() { return NumberCall(this); } + void set(float value) { make_call().set_value(value).perform(); } + + void add_on_state_callback(std::function &&callback); + + NumberTraits traits; + /// Return whether this number has gotten a full state yet. - bool has_state() const; - - /// Return with which interval the number is polled. Return 0 for non-polling mode. - virtual uint32_t update_interval(); - - void set_min_value(float min_value) { this->min_value_ = min_value; } - void set_max_value(float max_value) { this->max_value_ = max_value; } - void set_step(float step) { this->step_ = step; } - - float get_min_value() const { return this->min_value_; } - float get_max_value() const { return this->max_value_; } - float get_step() const { return this->step_; } + bool has_state() const { return has_state_; } protected: friend class NumberCall; @@ -95,17 +79,12 @@ class Number : public Nameable { * * @param value The value as validated by the NumberCall. */ - virtual void set(float value) = 0; + virtual void control(float value) = 0; uint32_t hash_base() override; CallbackManager state_callback_; - /// Override the icon advertised to Home Assistant, otherwise number's icon will be used. - optional icon_; bool has_state_{false}; - float step_{1.0}; - float min_value_{0}; - float max_value_{100}; }; } // namespace number diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index cf70a48c4d..557a01c6fa 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -4,6 +4,7 @@ import esphome.config_validation as cv from esphome.components import number from esphome.const import ( CONF_ID, + CONF_INITIAL_VALUE, CONF_LAMBDA, CONF_MAX_VALUE, CONF_MIN_VALUE, @@ -32,9 +33,10 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, + cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_INITIAL_VALUE): cv.float_, } ).extend(cv.polling_component_schema("60s")), validate_min_max, @@ -44,20 +46,27 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await number.register_number(var, config) + await number.register_number( + var, + config, + min_value=config[CONF_MIN_VALUE], + max_value=config[CONF_MAX_VALUE], + step=config[CONF_STEP], + ) if CONF_LAMBDA in config: template_ = await cg.process_lambda( config[CONF_LAMBDA], [], return_type=cg.optional.template(float) ) cg.add(var.set_template(template_)) + + elif CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) - - cg.add(var.set_min_value(config[CONF_MIN_VALUE])) - cg.add(var.set_max_value(config[CONF_MAX_VALUE])) - cg.add(var.set_step(config[CONF_STEP])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 69c5d62684..500f9f2272 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -6,34 +6,45 @@ namespace template_ { static const char *const TAG = "template.number"; -TemplateNumber::TemplateNumber() : set_trigger_(new Trigger()) {} +void TemplateNumber::setup() { + if (this->f_.has_value() || !this->optimistic_) + return; + + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + float value; + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } + this->publish_state(value); +} void TemplateNumber::update() { if (!this->f_.has_value()) return; auto val = (*this->f_)(); - if (val.has_value()) { - this->publish_state(*val); - } + if (!val.has_value()) + return; + + this->publish_state(*val); } -void TemplateNumber::set(float value) { +void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) + if (this->optimistic_) { this->publish_state(value); + this->pref_.save(&value); + } } -float TemplateNumber::get_setup_priority() const { return setup_priority::HARDWARE; } -void TemplateNumber::set_template(std::function()> &&f) { this->f_ = f; } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); LOG_UPDATE_INTERVAL(this); } -void TemplateNumber::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } -Trigger *TemplateNumber::get_set_trigger() const { return this->set_trigger_; }; - } // namespace template_ } // namespace esphome diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 4c633e3b53..50cd256b7f 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -3,27 +3,32 @@ #include "esphome/components/number/number.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/preferences.h" namespace esphome { namespace template_ { class TemplateNumber : public number::Number, public PollingComponent { public: - TemplateNumber(); - void set_template(std::function()> &&f); + void set_template(std::function()> &&f) { this->f_ = f; } + void setup() override; void update() override; void dump_config() override; - float get_setup_priority() const override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const; - void set_optimistic(bool optimistic); + Trigger *get_set_trigger() const { return set_trigger_; } + void set_optimistic(bool optimistic) { optimistic_ = optimistic; } + void set_initial_value(float initial_value) { initial_value_ = initial_value; } protected: - void set(float value) override; + void control(float value) override; bool optimistic_{false}; - Trigger *set_trigger_; + float initial_value_{NAN}; + Trigger *set_trigger_ = new Trigger(); optional()>> f_; + + ESPPreferenceObject pref_; }; } // namespace template_ diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 57eef7a946..b775d44211 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -614,8 +614,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM std::string WebServer::number_json(number::Number *obj, float value) { return json::build_json([obj, value](JsonObject &root) { root["id"] = "number-" + obj->get_object_id(); - std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); - root["state"] = state; + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%f", value); + root["state"] = buffer; root["value"] = value; }); } From 0a82e6e792477c579ae60f19ab593ec8fcd47369 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 10:28:23 +1200 Subject: [PATCH 052/105] Bump version to v1.20.0b4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 65c6458dd6..7f4f08274b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b3" +__version__ = "1.20.0b4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From c7a52c3894db640d4b5e793e542443d85766de1f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 15:40:42 +1200 Subject: [PATCH 053/105] Add restore_value to template number (#2041) --- .../components/template/number/__init__.py | 30 ++++++++++++++----- .../template/number/template_number.cpp | 23 ++++++++------ .../template/number/template_number.h | 2 ++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/esphome/components/template/number/__init__.py b/esphome/components/template/number/__init__.py index 557a01c6fa..22bbaacc15 100644 --- a/esphome/components/template/number/__init__.py +++ b/esphome/components/template/number/__init__.py @@ -9,6 +9,7 @@ from esphome.const import ( CONF_MAX_VALUE, CONF_MIN_VALUE, CONF_OPTIMISTIC, + CONF_RESTORE_VALUE, CONF_STEP, ) from .. import template_ns @@ -26,6 +27,17 @@ def validate_min_max(config): return config +def validate(config): + if CONF_LAMBDA in config: + if CONF_OPTIMISTIC in config: + raise cv.Invalid("optimistic cannot be used with lambda") + if CONF_INITIAL_VALUE in config: + raise cv.Invalid("initial_value cannot be used with lambda") + if CONF_RESTORE_VALUE in config: + raise cv.Invalid("restore_value cannot be used with lambda") + return config + + CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend( { @@ -33,13 +45,15 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_MAX_VALUE): cv.float_, cv.Required(CONF_MIN_VALUE): cv.float_, cv.Required(CONF_STEP): cv.positive_float, - cv.Exclusive(CONF_LAMBDA, "lambda-optimistic"): cv.returning_lambda, - cv.Exclusive(CONF_OPTIMISTIC, "lambda-optimistic"): cv.boolean, + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC): cv.boolean, cv.Optional(CONF_SET_ACTION): automation.validate_automation(single=True), cv.Optional(CONF_INITIAL_VALUE): cv.float_, + cv.Optional(CONF_RESTORE_VALUE): cv.boolean, } ).extend(cv.polling_component_schema("60s")), validate_min_max, + validate, ) @@ -60,13 +74,15 @@ async def to_code(config): ) cg.add(var.set_template(template_)) - elif CONF_OPTIMISTIC in config: - cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + else: + if CONF_OPTIMISTIC in config: + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + if CONF_INITIAL_VALUE in config: + cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) + if CONF_RESTORE_VALUE in config: + cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) if CONF_SET_ACTION in config: await automation.build_automation( var.get_set_trigger(), [(float, "x")], config[CONF_SET_ACTION] ) - - if CONF_INITIAL_VALUE in config: - cg.add(var.set_initial_value(config[CONF_INITIAL_VALUE])) diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 500f9f2272..eb9b17b976 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -7,16 +7,20 @@ namespace template_ { static const char *const TAG = "template.number"; void TemplateNumber::setup() { - if (this->f_.has_value() || !this->optimistic_) + if (this->f_.has_value()) return; - this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); float value; - if (!this->pref_.load(&value)) { - if (!isnan(this->initial_value_)) - value = this->initial_value_; - else - value = this->traits.get_min_value(); + if (!this->restore_value_) { + value = this->initial_value_; + } else { + this->pref_ = global_preferences.make_preference(this->get_object_id_hash()); + if (!this->pref_.load(&value)) { + if (!isnan(this->initial_value_)) + value = this->initial_value_; + else + value = this->traits.get_min_value(); + } } this->publish_state(value); } @@ -35,10 +39,11 @@ void TemplateNumber::update() { void TemplateNumber::control(float value) { this->set_trigger_->trigger(value); - if (this->optimistic_) { + if (this->optimistic_) this->publish_state(value); + + if (this->restore_value_) this->pref_.save(&value); - } } void TemplateNumber::dump_config() { LOG_NUMBER("", "Template Number", this); diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 50cd256b7f..9a82e44339 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -20,11 +20,13 @@ class TemplateNumber : public number::Number, public PollingComponent { Trigger *get_set_trigger() const { return set_trigger_; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_initial_value(float initial_value) { initial_value_ = initial_value; } + void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } protected: void control(float value) override; bool optimistic_{false}; float initial_value_{NAN}; + bool restore_value_{false}; Trigger *set_trigger_ = new Trigger(); optional()>> f_; From e25935ef213bdefedbe9b8358d9aaaab9ef6fa2c Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Tue, 20 Jul 2021 08:26:07 +0400 Subject: [PATCH 054/105] midea_ac: Fix turbo mode. Preset BOOST. (#2029) --- esphome/components/midea_ac/midea_frame.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index a84161b4af..3777f6fd77 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -102,8 +102,11 @@ class PropertiesFrame : public midea_dongle::BaseFrame { void set_sleep_mode(bool state) { this->set_bytemask_(20, 0x01, state); } /* TURBO MODE */ - bool get_turbo_mode() const { return this->pbuf_[18] & 0x20; } - void set_turbo_mode(bool state) { this->set_bytemask_(18, 0x20, state); } + bool get_turbo_mode() const { return this->pbuf_[18] & 0x20 || this->pbuf_[20] & 0x02; } + void set_turbo_mode(bool state) { + this->set_bytemask_(18, 0x20, state); + this->set_bytemask_(20, 0x02, state); + } /* FREEZE PROTECTION */ bool get_freeze_protection_mode() const { return this->pbuf_[31] & 0x80; } From 5c57b5137809770a5781f07495e44fe075ad2f1e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 19 Jul 2021 21:44:39 -0700 Subject: [PATCH 055/105] Bump dashboard to 20210719.0 (#2043) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c3562f492d..ab54828194 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210623.0 +esphome-dashboard==20210719.0 From 73ead5f3281a7042a09499cf3ccee27062bda096 Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 01:05:25 -0400 Subject: [PATCH 056/105] Correct ADS1115 handling of multiple sensors in continuous mode (#2016) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/ads1115/ads1115.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index d33ac83813..8cac897a15 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -107,17 +107,22 @@ float ADS1115Component::request_measurement(ADS1115Sensor *sensor) { } this->prev_config_ = config; - // about 1.6 ms with 860 samples per second + // about 1.2 ms with 860 samples per second delay(2); - uint32_t start = millis(); - while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { - if (millis() - start > 100) { - ESP_LOGW(TAG, "Reading ADS1115 timed out"); - this->status_set_warning(); - return NAN; + // in continuous mode, conversion will always be running, rely on the delay + // to ensure conversion is taking place with the correct settings + // can we use the rdy pin to trigger when a conversion is done? + if (!this->continuous_mode_) { + uint32_t start = millis(); + while (this->read_byte_16(ADS1115_REGISTER_CONFIG, &config) && (config >> 15) == 0) { + if (millis() - start > 100) { + ESP_LOGW(TAG, "Reading ADS1115 timed out"); + this->status_set_warning(); + return NAN; + } + yield(); } - yield(); } } From e5afb1c4eaf8c6a9f8603ff780ce9c70b2beede4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Tue, 20 Jul 2021 07:05:56 +0200 Subject: [PATCH 057/105] ESP32 ADC use esp-idf (#2024) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/adc/adc_sensor.cpp | 52 +++++++++++++++++++++------ esphome/components/adc/adc_sensor.h | 8 +++-- esphome/components/adc/sensor.py | 8 ++--- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 960d9ed8e2..d6469ab785 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -11,7 +11,30 @@ namespace adc { static const char *const TAG = "adc"; #ifdef ARDUINO_ARCH_ESP32 -void ADCSensor::set_attenuation(adc_attenuation_t attenuation) { this->attenuation_ = attenuation; } +void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; } + +inline adc1_channel_t gpio_to_adc1(uint8_t pin) { + switch (pin) { + case 36: + return ADC1_CHANNEL_0; + case 37: + return ADC1_CHANNEL_1; + case 38: + return ADC1_CHANNEL_2; + case 39: + return ADC1_CHANNEL_3; + case 32: + return ADC1_CHANNEL_4; + case 33: + return ADC1_CHANNEL_5; + case 34: + return ADC1_CHANNEL_6; + case 35: + return ADC1_CHANNEL_7; + default: + return ADC1_CHANNEL_MAX; + } +} #endif void ADCSensor::setup() { @@ -21,7 +44,9 @@ void ADCSensor::setup() { #endif #ifdef ARDUINO_ARCH_ESP32 - analogSetPinAttenuation(this->pin_, this->attenuation_); + adc1_config_channel_atten(gpio_to_adc1(pin_), attenuation_); + adc1_config_width(ADC_WIDTH_BIT_12); + adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_)); #endif } void ADCSensor::dump_config() { @@ -36,18 +61,20 @@ void ADCSensor::dump_config() { #ifdef ARDUINO_ARCH_ESP32 ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_); switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)"); break; - case ADC_6db: + case ADC_ATTEN_DB_6: ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)"); break; - case ADC_11db: + case ADC_ATTEN_DB_11: ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)"); break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } #endif LOG_UPDATE_INTERVAL(this); @@ -60,20 +87,23 @@ void ADCSensor::update() { } float ADCSensor::sample() { #ifdef ARDUINO_ARCH_ESP32 - float value_v = analogRead(this->pin_) / 4095.0f; // NOLINT + int raw = adc1_get_raw(gpio_to_adc1(pin_)); + float value_v = raw / 4095.0f; switch (this->attenuation_) { - case ADC_0db: + case ADC_ATTEN_DB_0: value_v *= 1.1; break; - case ADC_2_5db: + case ADC_ATTEN_DB_2_5: value_v *= 1.5; break; - case ADC_6db: + case ADC_ATTEN_DB_6: value_v *= 2.2; break; - case ADC_11db: + case ADC_ATTEN_DB_11: value_v *= 3.9; break; + default: // This is to satisfy the unused ADC_ATTEN_MAX + break; } return value_v; #endif diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h index 3a08ff6be4..4591ed758d 100644 --- a/esphome/components/adc/adc_sensor.h +++ b/esphome/components/adc/adc_sensor.h @@ -6,6 +6,10 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/voltage_sampler/voltage_sampler.h" +#ifdef ARDUINO_ARCH_ESP32 +#include "driver/adc.h" +#endif + namespace esphome { namespace adc { @@ -13,7 +17,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage public: #ifdef ARDUINO_ARCH_ESP32 /// Set the attenuation for this pin. Only available on the ESP32. - void set_attenuation(adc_attenuation_t attenuation); + void set_attenuation(adc_atten_t attenuation); #endif /// Update adc values. @@ -34,7 +38,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage uint8_t pin_; #ifdef ARDUINO_ARCH_ESP32 - adc_attenuation_t attenuation_{ADC_0db}; + adc_atten_t attenuation_{ADC_ATTEN_DB_0}; #endif }; diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index 90561679b7..7a944a7260 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -16,10 +16,10 @@ from esphome.const import ( AUTO_LOAD = ["voltage_sampler"] ATTENUATION_MODES = { - "0db": cg.global_ns.ADC_0db, - "2.5db": cg.global_ns.ADC_2_5db, - "6db": cg.global_ns.ADC_6db, - "11db": cg.global_ns.ADC_11db, + "0db": cg.global_ns.ADC_ATTEN_DB_0, + "2.5db": cg.global_ns.ADC_ATTEN_DB_2_5, + "6db": cg.global_ns.ADC_ATTEN_DB_6, + "11db": cg.global_ns.ADC_ATTEN_DB_11, } From fa72990a633025f612aef937e72877904141e42d Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jul 2021 17:09:58 +1200 Subject: [PATCH 058/105] Bump version to v1.20.0b5 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 7f4f08274b..556c8c70fc 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b4" +__version__ = "1.20.0b5" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 35b5c1ed5600c4dc814bc328d24b33799d4d4b04 Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 20 Jul 2021 22:42:03 +0200 Subject: [PATCH 059/105] Fix white value transition for addressable lights (#2045) --- esphome/components/light/addressable_light.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/light/addressable_light.cpp b/esphome/components/light/addressable_light.cpp index ea24736c63..4ef293cd03 100644 --- a/esphome/components/light/addressable_light.cpp +++ b/esphome/components/light/addressable_light.cpp @@ -68,10 +68,7 @@ void AddressableLight::write_state(LightState *state) { // our transition will handle brightness, disable brightness in correction. this->correction_.set_local_brightness(255); - uint8_t orig_w = target_color.w; target_color *= static_cast(roundf(end_values.get_brightness() * end_values.get_state() * 255.0f)); - // w is not scaled by brightness - target_color.w = orig_w; float denom = (1.0f - new_smoothed); float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom; From e19aa3bbe0ca9d3cacd92cc011a8e01a553f6965 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 09:20:20 +1200 Subject: [PATCH 060/105] Adding last_reset_type to sensors that should support it. (#2039) --- esphome/components/api/api.proto | 7 ++++++ esphome/components/api/api_connection.cpp | 1 + esphome/components/api/api_pb2.cpp | 21 ++++++++++++++++++ esphome/components/api/api_pb2.h | 6 ++++++ esphome/components/atm90e32/sensor.py | 15 +++++++++++-- esphome/components/havells_solar/sensor.py | 7 ++++-- esphome/components/hlw8012/sensor.py | 9 ++++++-- esphome/components/pzem004t/sensor.py | 5 +++-- esphome/components/pzemac/sensor.py | 5 +++-- esphome/components/sdm_meter/sensor.py | 22 ++++++++++++++----- esphome/components/sensor/__init__.py | 25 ++++++++++++++++++++++ esphome/components/sensor/sensor.cpp | 13 +++++++++++ esphome/components/sensor/sensor.h | 24 +++++++++++++++++++++ esphome/const.py | 8 +++++++ 14 files changed, 153 insertions(+), 15 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index 40be1fd0db..073775ed2e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -422,6 +422,12 @@ enum SensorStateClass { STATE_CLASS_MEASUREMENT = 1; } +enum SensorLastResetType { + LAST_RESET_NONE = 0; + LAST_RESET_NEVER = 1; + LAST_RESET_AUTO = 2; +} + message ListEntitiesSensorResponse { option (id) = 16; option (source) = SOURCE_SERVER; @@ -438,6 +444,7 @@ message ListEntitiesSensorResponse { bool force_update = 8; string device_class = 9; SensorStateClass state_class = 10; + SensorLastResetType last_reset_type = 11; } message SensorStateResponse { option (id) = 25; diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 79ffcfa69e..fb05772e5e 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -399,6 +399,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) { msg.force_update = sensor->get_force_update(); msg.device_class = sensor->get_device_class(); msg.state_class = static_cast(sensor->state_class); + msg.last_reset_type = static_cast(sensor->last_reset_type); return this->send_list_entities_sensor_response(msg); } diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index c3cfc8cd76..057d71324f 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -72,6 +72,18 @@ template<> const char *proto_enum_to_string(enums::Sens return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::SensorLastResetType value) { + switch (value) { + case enums::LAST_RESET_NONE: + return "LAST_RESET_NONE"; + case enums::LAST_RESET_NEVER: + return "LAST_RESET_NEVER"; + case enums::LAST_RESET_AUTO: + return "LAST_RESET_AUTO"; + default: + return "UNKNOWN"; + } +} template<> const char *proto_enum_to_string(enums::LogLevel value) { switch (value) { case enums::LOG_LEVEL_NONE: @@ -1592,6 +1604,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va this->state_class = value.as_enum(); return true; } + case 11: { + this->last_reset_type = value.as_enum(); + return true; + } default: return false; } @@ -1647,6 +1663,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(8, this->force_update); buffer.encode_string(9, this->device_class); buffer.encode_enum(10, this->state_class); + buffer.encode_enum(11, this->last_reset_type); } void ListEntitiesSensorResponse::dump_to(std::string &out) const { char buffer[64]; @@ -1692,6 +1709,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const { out.append(" state_class: "); out.append(proto_enum_to_string(this->state_class)); out.append("\n"); + + out.append(" last_reset_type: "); + out.append(proto_enum_to_string(this->last_reset_type)); + out.append("\n"); out.append("}"); } bool SensorStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e3bb1d9106..0551508b4b 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -36,6 +36,11 @@ enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, }; +enum SensorLastResetType : uint32_t { + LAST_RESET_NONE = 0, + LAST_RESET_NEVER = 1, + LAST_RESET_AUTO = 2, +}; enum LogLevel : uint32_t { LOG_LEVEL_NONE = 0, LOG_LEVEL_ERROR = 1, @@ -429,6 +434,7 @@ class ListEntitiesSensorResponse : public ProtoMessage { bool force_update{false}; std::string device_class{}; enums::SensorStateClass state_class{}; + enums::SensorLastResetType last_reset_type{}; void encode(ProtoWriteBuffer buffer) const override; void dump_to(std::string &out) const override; diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py index 68ec199bff..2c34d76b52 100644 --- a/esphome/components/atm90e32/sensor.py +++ b/esphome/components/atm90e32/sensor.py @@ -21,6 +21,7 @@ from esphome.const import ( ICON_EMPTY, ICON_LIGHTBULB, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, UNIT_HERTZ, UNIT_VOLT, @@ -91,10 +92,20 @@ ATM90E32_PHASE_SCHEMA = cv.Schema( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_MEASUREMENT + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py index 7d1e2be581..1926d4d68a 100644 --- a/esphome/components/havells_solar/sensor.py +++ b/esphome/components/havells_solar/sensor.py @@ -15,6 +15,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, STATE_CLASS_NONE, UNIT_AMPERE, @@ -121,14 +122,16 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( UNIT_KILOWATT_HOURS, ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( UNIT_HOURS, diff --git a/esphome/components/hlw8012/sensor.py b/esphome/components/hlw8012/sensor.py index e24e995eba..face32872e 100644 --- a/esphome/components/hlw8012/sensor.py +++ b/esphome/components/hlw8012/sensor.py @@ -19,8 +19,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -67,7 +67,12 @@ CONFIG_SCHEMA = cv.Schema( UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 1, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 1, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_CURRENT_RESISTOR, default=0.001): cv.resistance, cv.Optional(CONF_VOLTAGE_DIVIDER, default=2351): cv.positive_float, diff --git a/esphome/components/pzem004t/sensor.py b/esphome/components/pzem004t/sensor.py index e3859f090c..b358b8c650 100644 --- a/esphome/components/pzem004t/sensor.py +++ b/esphome/components/pzem004t/sensor.py @@ -12,8 +12,8 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT, @@ -47,7 +47,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/pzemac/sensor.py b/esphome/components/pzemac/sensor.py index 778c5054a0..1dd77a0371 100644 --- a/esphome/components/pzemac/sensor.py +++ b/esphome/components/pzemac/sensor.py @@ -17,8 +17,8 @@ from esphome.const import ( DEVICE_CLASS_ENERGY, ICON_EMPTY, ICON_CURRENT_AC, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_HERTZ, UNIT_VOLT, UNIT_AMPERE, @@ -54,7 +54,8 @@ CONFIG_SCHEMA = ( ICON_EMPTY, 0, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( UNIT_HERTZ, diff --git a/esphome/components/sdm_meter/sensor.py b/esphome/components/sdm_meter/sensor.py index 39ef280fef..ce560b9d4b 100644 --- a/esphome/components/sdm_meter/sensor.py +++ b/esphome/components/sdm_meter/sensor.py @@ -25,8 +25,8 @@ from esphome.const import ( ICON_CURRENT_AC, ICON_EMPTY, ICON_FLASH, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_AMPERE, UNIT_DEGREES, UNIT_EMPTY, @@ -88,24 +88,36 @@ CONFIG_SCHEMA = ( STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema( - UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, STATE_CLASS_NONE + UNIT_WATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema( UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 89bde9476a..0a0c3a9214 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -17,6 +17,7 @@ from esphome.const import ( CONF_ICON, CONF_ID, CONF_INTERNAL, + CONF_LAST_RESET_TYPE, CONF_ON_RAW_VALUE, CONF_ON_VALUE, CONF_ON_VALUE_RANGE, @@ -30,6 +31,9 @@ from esphome.const import ( CONF_NAME, CONF_MQTT_ID, CONF_FORCE_UPDATE, + LAST_RESET_TYPE_AUTO, + LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_NONE, UNIT_EMPTY, ICON_EMPTY, DEVICE_CLASS_EMPTY, @@ -79,6 +83,15 @@ STATE_CLASSES = { } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") +LastResetTypes = sensor_ns.enum("LastResetType") +LAST_RESET_TYPES = { + LAST_RESET_TYPE_NONE: LastResetTypes.LAST_RESET_TYPE_NONE, + LAST_RESET_TYPE_NEVER: LastResetTypes.LAST_RESET_TYPE_NEVER, + LAST_RESET_TYPE_AUTO: LastResetTypes.LAST_RESET_TYPE_AUTO, +} +validate_last_reset_type = cv.enum(LAST_RESET_TYPES, lower=True, space="_") + + IS_PLATFORM_COMPONENT = True @@ -168,6 +181,7 @@ SENSOR_SCHEMA = cv.MQTT_COMPONENT_SCHEMA.extend( cv.Optional(CONF_ACCURACY_DECIMALS): accuracy_decimals, cv.Optional(CONF_DEVICE_CLASS): device_class, cv.Optional(CONF_STATE_CLASS): validate_state_class, + cv.Optional(CONF_LAST_RESET_TYPE): validate_last_reset_type, cv.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, cv.Optional(CONF_EXPIRE_AFTER): cv.All( cv.requires_component("mqtt"), @@ -202,6 +216,7 @@ def sensor_schema( accuracy_decimals_: int, device_class_: Optional[str] = DEVICE_CLASS_EMPTY, state_class_: Optional[str] = STATE_CLASS_NONE, + last_reset_type_: Optional[str] = LAST_RESET_TYPE_NONE, ) -> cv.Schema: schema = SENSOR_SCHEMA if unit_of_measurement_ != UNIT_EMPTY: @@ -230,6 +245,14 @@ def sensor_schema( schema = schema.extend( {cv.Optional(CONF_STATE_CLASS, default=state_class_): validate_state_class} ) + if last_reset_type_ != LAST_RESET_TYPE_NONE: + schema = schema.extend( + { + cv.Optional( + CONF_LAST_RESET_TYPE, default=last_reset_type_ + ): validate_last_reset_type + } + ) return schema @@ -479,6 +502,8 @@ async def setup_sensor_core_(var, config): cg.add(var.set_icon(config[CONF_ICON])) if CONF_ACCURACY_DECIMALS in config: cg.add(var.set_accuracy_decimals(config[CONF_ACCURACY_DECIMALS])) + if CONF_LAST_RESET_TYPE in config: + cg.add(var.set_last_reset_type(config[CONF_LAST_RESET_TYPE])) cg.add(var.set_force_update(config[CONF_FORCE_UPDATE])) if config.get(CONF_FILTERS): # must exist and not be empty filters = await build_filters(config[CONF_FILTERS]) diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index fe92f88308..6e8765a8df 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -16,6 +16,18 @@ const char *state_class_to_string(StateClass state_class) { } } +const char *last_reset_type_to_string(LastResetType last_reset_type) { + switch (last_reset_type) { + case LAST_RESET_TYPE_NEVER: + return "never"; + case LAST_RESET_TYPE_AUTO: + return "auto"; + case LAST_RESET_TYPE_NONE: + default: + return ""; + } +} + void Sensor::publish_state(float state) { this->raw_state = state; this->raw_callback_.call(state); @@ -64,6 +76,7 @@ void Sensor::set_state_class(const std::string &state_class) { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } } +void Sensor::set_last_reset_type(LastResetType last_reset_type) { this->last_reset_type = last_reset_type; } std::string Sensor::get_unit_of_measurement() { if (this->unit_of_measurement_.has_value()) return *this->unit_of_measurement_; diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 123e7eddb3..b9908b6cbe 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -14,6 +14,10 @@ namespace sensor { ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ } \ ESP_LOGCONFIG(TAG, "%s State Class: '%s'", prefix, state_class_to_string((obj)->state_class)); \ + if ((obj)->state_class == sensor::STATE_CLASS_MEASUREMENT && \ + (obj)->last_reset_type != sensor::LAST_RESET_TYPE_NONE) { \ + ESP_LOGCONFIG(TAG, "%s Last Reset Type: '%s'", prefix, last_reset_type_to_string((obj)->last_reset_type)); \ + } \ ESP_LOGCONFIG(TAG, "%s Unit of Measurement: '%s'", prefix, (obj)->get_unit_of_measurement().c_str()); \ ESP_LOGCONFIG(TAG, "%s Accuracy Decimals: %d", prefix, (obj)->get_accuracy_decimals()); \ if (!(obj)->get_icon().empty()) { \ @@ -37,6 +41,20 @@ enum StateClass : uint8_t { const char *state_class_to_string(StateClass state_class); +/** + * Sensor last reset types + */ +enum LastResetType : uint8_t { + /// This sensor does not support resetting. ie, it is not accumulative + LAST_RESET_TYPE_NONE = 0, + /// This sensor is expected to never reset its value + LAST_RESET_TYPE_NEVER = 1, + /// This sensor may reset and Home Assistant will watch for this + LAST_RESET_TYPE_AUTO = 2, +}; + +const char *last_reset_type_to_string(LastResetType last_reset_type); + /** Base-class for all sensors. * * A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy. @@ -155,6 +173,12 @@ class Sensor : public Nameable { */ virtual std::string device_class(); + // The Last reset type of this sensor + LastResetType last_reset_type{LAST_RESET_TYPE_NONE}; + + /// Manually set the Home Assistant last reset type for this sensor. + void set_last_reset_type(LastResetType last_reset_type); + /** A unique ID for this sensor, empty for no unique id. See unique ID requirements: * https://developers.home-assistant.io/docs/en/entity_registry_index.html#unique-id-requirements * diff --git a/esphome/const.py b/esphome/const.py index 556c8c70fc..f85016aeb5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -297,6 +297,7 @@ CONF_KEY = "key" CONF_LAMBDA = "lambda" CONF_LAST_CONFIDENCE = "last_confidence" CONF_LAST_FINGER_ID = "last_finger_id" +CONF_LAST_RESET_TYPE = "last_reset_type" CONF_LATITUDE = "latitude" CONF_LENGTH = "length" CONF_LEVEL = "level" @@ -785,3 +786,10 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" + +# This sensor does not support resetting. ie, it is not accumulative +LAST_RESET_TYPE_NONE = "" +# This sensor is expected to never reset its value +LAST_RESET_TYPE_NEVER = "never" +# This sensor may reset and Home Assistant will watch for this +LAST_RESET_TYPE_AUTO = "auto" From 837930234fe0ba3df3d5f22d1b648d7ab9082a8f Mon Sep 17 00:00:00 2001 From: Sean Vig Date: Tue, 20 Jul 2021 17:35:45 -0400 Subject: [PATCH 061/105] Remove superfluous polling on ADS1115 (#2015) --- esphome/components/ads1115/ads1115.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/esphome/components/ads1115/ads1115.cpp b/esphome/components/ads1115/ads1115.cpp index 8cac897a15..92f0ffbe3a 100644 --- a/esphome/components/ads1115/ads1115.cpp +++ b/esphome/components/ads1115/ads1115.cpp @@ -64,11 +64,6 @@ void ADS1115Component::setup() { return; } this->prev_config_ = config; - - for (auto *sensor : this->sensors_) { - this->set_interval(sensor->get_name(), sensor->update_interval(), - [this, sensor] { this->request_measurement(sensor); }); - } } void ADS1115Component::dump_config() { ESP_LOGCONFIG(TAG, "Setting up ADS1115..."); From 27112e2ace456eea883ee37d13d8ea970428908f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jul 2021 10:52:48 +1200 Subject: [PATCH 062/105] Bump version to v1.20.0b6 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index f85016aeb5..d8a4448d1f 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b5" +__version__ = "1.20.0b6" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 4b8ec44262922e056c6ab84894d75c2e9575e293 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Jul 2021 07:55:49 +1200 Subject: [PATCH 063/105] Bump version to v1.20.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d8a4448d1f..48790cf16a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0b6" +__version__ = "1.20.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From caa651e55badb77b3f3bac20346ef1384a473569 Mon Sep 17 00:00:00 2001 From: Maurice Makaay Date: Thu, 22 Jul 2021 14:37:42 +0200 Subject: [PATCH 064/105] Accept change as proposed by black. (#2055) --- esphome/components/external_components/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index bf44dc1929..3e833d0b66 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -109,9 +109,9 @@ def _compute_destination_path(key: str) -> Path: return base_dir / h.hexdigest()[:8] -def _run_git_command(cmd): +def _run_git_command(cmd, cwd=None): try: - ret = subprocess.run(cmd, capture_output=True, check=False) + ret = subprocess.run(cmd, cwd=cwd, capture_output=True, check=False) except FileNotFoundError as err: raise cv.Invalid( "git is not installed but required for external_components.\n" @@ -151,14 +151,16 @@ def _process_git_config(config: dict, refresh) -> str: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) - _run_git_command(["git", "stash", "push", "--include-untracked"]) + _run_git_command( + ["git", "stash", "push", "--include-untracked"], str(repo_dir) + ) # Fetch remote ref cmd = ["git", "fetch", "--", "origin"] if CONF_REF in config: cmd.append(config[CONF_REF]) - _run_git_command(cmd) + _run_git_command(cmd, str(repo_dir)) # Hard reset to FETCH_HEAD (short-lived git ref corresponding to most recent fetch) - _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"]) + _run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir)) if (repo_dir / "esphome" / "components").is_dir(): components_dir = repo_dir / "esphome" / "components" From 1efabd27d88d1596d79a9b61fddc7a18a83bb7fc Mon Sep 17 00:00:00 2001 From: "Sergey V. DUDANOV" Date: Thu, 22 Jul 2021 16:39:21 +0400 Subject: [PATCH 065/105] midea_ac: fix presets implementation (#2054) --- esphome/components/midea_ac/midea_climate.cpp | 4 ++-- esphome/components/midea_ac/midea_frame.cpp | 22 ++++++++++++------- esphome/components/midea_ac/midea_frame.h | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/esphome/components/midea_ac/midea_climate.cpp b/esphome/components/midea_ac/midea_climate.cpp index 9fe5df7de3..72f7d23404 100644 --- a/esphome/components/midea_ac/midea_climate.cpp +++ b/esphome/components/midea_ac/midea_climate.cpp @@ -100,7 +100,7 @@ bool MideaAC::allow_preset(climate::ClimatePreset preset) const { ESP_LOGD(TAG, "BOOST preset is only available in HEAT or COOL mode"); } break; - case climate::CLIMATE_PRESET_HOME: + case climate::CLIMATE_PRESET_NONE: return true; default: break; @@ -191,7 +191,7 @@ climate::ClimateTraits MideaAC::traits() { if (traits_swing_both_) traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH); traits.set_supported_presets({ - climate::CLIMATE_PRESET_HOME, + climate::CLIMATE_PRESET_NONE, }); if (traits_preset_eco_) traits.add_supported_preset(climate::CLIMATE_PRESET_ECO); diff --git a/esphome/components/midea_ac/midea_frame.cpp b/esphome/components/midea_ac/midea_frame.cpp index 5f09f4314f..c0a5ce4b55 100644 --- a/esphome/components/midea_ac/midea_frame.cpp +++ b/esphome/components/midea_ac/midea_frame.cpp @@ -86,18 +86,17 @@ void PropertiesFrame::set_mode(climate::ClimateMode mode) { } optional PropertiesFrame::get_preset() const { - if (this->get_eco_mode()) { + if (this->get_eco_mode()) return climate::CLIMATE_PRESET_ECO; - } else if (this->get_sleep_mode()) { + if (this->get_sleep_mode()) return climate::CLIMATE_PRESET_SLEEP; - } else if (this->get_turbo_mode()) { + if (this->get_turbo_mode()) return climate::CLIMATE_PRESET_BOOST; - } else { - return climate::CLIMATE_PRESET_HOME; - } + return climate::CLIMATE_PRESET_NONE; } void PropertiesFrame::set_preset(climate::ClimatePreset preset) { + this->clear_presets(); switch (preset) { case climate::CLIMATE_PRESET_ECO: this->set_eco_mode(true); @@ -113,14 +112,21 @@ void PropertiesFrame::set_preset(climate::ClimatePreset preset) { } } +void PropertiesFrame::clear_presets() { + this->set_eco_mode(false); + this->set_sleep_mode(false); + this->set_turbo_mode(false); + this->set_freeze_protection_mode(false); +} + bool PropertiesFrame::is_custom_preset() const { return this->get_freeze_protection_mode(); } const std::string &PropertiesFrame::get_custom_preset() const { return midea_ac::MIDEA_FREEZE_PROTECTION_PRESET; }; void PropertiesFrame::set_custom_preset(const std::string &preset) { - if (preset == MIDEA_FREEZE_PROTECTION_PRESET) { + this->clear_presets(); + if (preset == MIDEA_FREEZE_PROTECTION_PRESET) this->set_freeze_protection_mode(true); - } } bool PropertiesFrame::is_custom_fan_mode() const { diff --git a/esphome/components/midea_ac/midea_frame.h b/esphome/components/midea_ac/midea_frame.h index 3777f6fd77..e1d6fed49d 100644 --- a/esphome/components/midea_ac/midea_frame.h +++ b/esphome/components/midea_ac/midea_frame.h @@ -115,6 +115,7 @@ class PropertiesFrame : public midea_dongle::BaseFrame { /* PRESET */ optional get_preset() const; void set_preset(climate::ClimatePreset preset); + void clear_presets(); bool is_custom_preset() const; const std::string &get_custom_preset() const; From fda8dd4ce35671b7a8fe8b4de6a335bd31f349d0 Mon Sep 17 00:00:00 2001 From: carstenschroeder Date: Thu, 22 Jul 2021 14:39:57 +0200 Subject: [PATCH 066/105] Fixes new auto mode COOL and HEAT after #1994 (#2053) --- esphome/components/pid/pid_climate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index dac4426698..ef8a4df962 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -35,8 +35,8 @@ void PIDClimate::control(const climate::ClimateCall &call) { if (call.get_target_temperature().has_value()) this->target_temperature = *call.get_target_temperature(); - // If switching to non-auto mode, set output immediately - if (this->mode != climate::CLIMATE_MODE_HEAT_COOL) + // If switching to off mode, set output immediately + if (this->mode == climate::CLIMATE_MODE_OFF) this->handle_non_auto_mode_(); this->publish_state(); From 64a3aa7092e6470c57e432681628811997f65c9b Mon Sep 17 00:00:00 2001 From: buxtronix Date: Mon, 26 Jul 2021 18:41:54 +1000 Subject: [PATCH 067/105] Log warning about lack of support for Anova nano (#2063) Co-authored-by: Ben Buxton --- esphome/components/anova/anova.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index c4b08ca6b5..63e0710936 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -60,6 +60,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); + ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); break; } this->char_handle_ = chr->handle; From 1e2a9e8348eb01a40a21a5fc06cf34241ed451dd Mon Sep 17 00:00:00 2001 From: Chris Nussbaum Date: Mon, 26 Jul 2021 04:39:03 -0500 Subject: [PATCH 068/105] Couple more updates for the Tuya component (#2065) Co-authored-by: Chris Nussbaum --- esphome/components/tuya/tuya.cpp | 40 ++++++++++++++++++++++++++++---- esphome/components/tuya/tuya.h | 1 + 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index d9a0a9932a..916a550675 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -8,9 +8,10 @@ namespace tuya { static const char *const TAG = "tuya"; static const int COMMAND_DELAY = 50; +static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { - this->set_interval("heartbeat", 10000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); } void Tuya::loop() { @@ -117,7 +118,13 @@ void Tuya::handle_char_(uint8_t c) { } void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) { - switch ((TuyaCommandType) command) { + TuyaCommandType command_type = (TuyaCommandType) command; + + if (this->expected_response_.has_value() && this->expected_response_ == command_type) { + this->expected_response_.reset(); + } + + switch (command_type) { case TuyaCommandType::HEARTBEAT: ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); this->protocol_version_ = version; @@ -316,6 +323,25 @@ void Tuya::send_raw_command_(TuyaCommand command) { uint8_t version = 0; this->last_command_timestamp_ = millis(); + switch (command.cmd) { + case TuyaCommandType::HEARTBEAT: + this->expected_response_ = TuyaCommandType::HEARTBEAT; + break; + case TuyaCommandType::PRODUCT_QUERY: + this->expected_response_ = TuyaCommandType::PRODUCT_QUERY; + break; + case TuyaCommandType::CONF_QUERY: + this->expected_response_ = TuyaCommandType::CONF_QUERY; + break; + case TuyaCommandType::DATAPOINT_DELIVER: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + case TuyaCommandType::DATAPOINT_QUERY: + this->expected_response_ = TuyaCommandType::DATAPOINT_REPORT; + break; + default: + break; + } ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), version, hexencode(command.payload).c_str(), static_cast(this->init_state_)); @@ -332,8 +358,14 @@ void Tuya::send_raw_command_(TuyaCommand command) { void Tuya::process_command_queue_() { uint32_t delay = millis() - this->last_command_timestamp_; + + if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { + this->expected_response_.reset(); + } + // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly - if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty()) { + if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() && + !this->expected_response_.has_value()) { this->send_raw_command_(command_queue_.front()); this->command_queue_.erase(command_queue_.begin()); } @@ -345,7 +377,7 @@ void Tuya::send_command_(const TuyaCommand &command) { } void Tuya::send_empty_command_(TuyaCommandType command) { - send_command_(TuyaCommand{.cmd = command, .payload = std::vector{0x04}}); + send_command_(TuyaCommand{.cmd = command, .payload = std::vector{}}); } void Tuya::send_wifi_status_() { diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 4ca4f56366..68decf7e9e 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -113,6 +113,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector rx_message_; std::vector ignore_mcu_update_on_datapoints_{}; std::vector command_queue_; + optional expected_response_{}; uint8_t wifi_status_ = -1; }; From 71ded24fced532004c726c9578021e1cd7b0b8f6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 21:46:13 +0200 Subject: [PATCH 069/105] Fix MQTT climate custom fan modes without regular ones (#2071) --- esphome/components/mqtt/mqtt_climate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index ab8354e66c..5809b6616c 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -72,7 +72,7 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC root["act_t"] = this->get_action_state_topic(); } - if (traits.get_supports_fan_modes()) { + if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) { // fan_mode_command_topic root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic(); // fan_mode_state_topic From 887081fd710c80c2aabe9954d19d3f25b19f6424 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 27 Jul 2021 09:43:05 +1200 Subject: [PATCH 070/105] Bump version to v1.20.1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 48790cf16a..a8e9e9cfc4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.0" +__version__ = "1.20.1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From bfac6607d16cf6f7d1b5a3874a1a0793cc916e33 Mon Sep 17 00:00:00 2001 From: "John K. Luebs" Date: Tue, 27 Jul 2021 21:01:15 -0500 Subject: [PATCH 071/105] More Tuya MCU robustness (#2080) --- esphome/components/tuya/tuya.cpp | 11 +++++++++-- esphome/components/tuya/tuya.h | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 916a550675..325f12a58d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -7,7 +7,7 @@ namespace esphome { namespace tuya { static const char *const TAG = "tuya"; -static const int COMMAND_DELAY = 50; +static const int COMMAND_DELAY = 10; static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { @@ -114,6 +114,8 @@ void Tuya::handle_char_(uint8_t c) { this->rx_message_.push_back(c); if (!this->validate_message_()) { this->rx_message_.clear(); + } else { + this->last_rx_char_timestamp_ = millis(); } } @@ -357,7 +359,12 @@ void Tuya::send_raw_command_(TuyaCommand command) { } void Tuya::process_command_queue_() { - uint32_t delay = millis() - this->last_command_timestamp_; + uint32_t now = millis(); + uint32_t delay = now - this->last_command_timestamp_; + + if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) { + this->rx_message_.clear(); + } if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) { this->expected_response_.reset(); diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 68decf7e9e..7ce4be4315 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -107,6 +107,7 @@ class Tuya : public Component, public uart::UARTDevice { int gpio_status_ = -1; int gpio_reset_ = -1; uint32_t last_command_timestamp_ = 0; + uint32_t last_rx_char_timestamp_ = 0; std::string product_ = ""; std::vector listeners_; std::vector datapoints_; From c2f9ed7c59ded423a25710cc5841fcfc948b7fa8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jul 2021 15:18:31 +1200 Subject: [PATCH 072/105] Bump esphome dashboard to 20210728.0 (#2081) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab54828194..561bf0f4d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,4 +11,4 @@ ifaddr==0.1.7 platformio==5.1.1 esptool==2.8 click==7.1.2 -esphome-dashboard==20210719.0 +esphome-dashboard==20210728.0 From be4c71885932266aaff51b94f3127943ed5072b2 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 07:53:54 +1200 Subject: [PATCH 073/105] HLW8012 - Dump energy sensor config (#2082) --- esphome/components/hlw8012/hlw8012.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/hlw8012/hlw8012.cpp b/esphome/components/hlw8012/hlw8012.cpp index 79c25a45b0..ecdaa07ab2 100644 --- a/esphome/components/hlw8012/hlw8012.cpp +++ b/esphome/components/hlw8012/hlw8012.cpp @@ -45,6 +45,7 @@ void HLW8012Component::dump_config() { LOG_SENSOR(" ", "Voltage", this->voltage_sensor_) LOG_SENSOR(" ", "Current", this->current_sensor_) LOG_SENSOR(" ", "Power", this->power_sensor_) + LOG_SENSOR(" ", "Energy", this->energy_sensor_) } float HLW8012Component::get_setup_priority() const { return setup_priority::DATA; } void HLW8012Component::update() { From 62f3039d82c2996ee3e8f2e3a7d70e5a917adbdb Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:46:15 +1200 Subject: [PATCH 074/105] Use sensor_schema for total_daily_energy (#2090) Co-authored-by: Otto Winter --- .../components/total_daily_energy/sensor.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index b70a0cece9..7fdd176d42 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -1,7 +1,15 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, time -from esphome.const import CONF_ID, CONF_TIME_ID +from esphome.const import ( + CONF_ID, + CONF_TIME_ID, + DEVICE_CLASS_ENERGY, + ICON_EMPTY, + LAST_RESET_TYPE_AUTO, + STATE_CLASS_MEASUREMENT, + UNIT_EMPTY, +) DEPENDENCIES = ["time"] @@ -11,13 +19,24 @@ TotalDailyEnergy = total_daily_energy_ns.class_( "TotalDailyEnergy", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), - cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), - cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + sensor.sensor_schema( + UNIT_EMPTY, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(TotalDailyEnergy), + cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), + cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): From 456824669ff1bdfac7c0ec6fb4a0011c2683b407 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 29 Jul 2021 19:51:17 +1200 Subject: [PATCH 075/105] Bump version to v1.20.2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a8e9e9cfc4..1c1e9eb3a5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.1" +__version__ = "1.20.2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 1d56f0b035961cc80d20162fe2cb9c08b77b8005 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 10:53:33 +1200 Subject: [PATCH 076/105] Set pulse meter total to use state class measurement and last reset type auto (#2097) --- esphome/components/pulse_meter/sensor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index e732971c3a..4b8e8a1be0 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -11,8 +11,8 @@ from esphome.const import ( CONF_TOTAL, CONF_VALUE, ICON_PULSE, + LAST_RESET_TYPE_AUTO, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_PULSES, UNIT_PULSES_PER_MINUTE, DEVICE_CLASS_EMPTY, @@ -59,7 +59,12 @@ CONFIG_SCHEMA = sensor.sensor_schema( cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, cv.Optional(CONF_TOTAL): sensor.sensor_schema( - UNIT_PULSES, ICON_PULSE, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + UNIT_PULSES, + ICON_PULSE, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + LAST_RESET_TYPE_AUTO, ), } ) From fee446c28a5b331477fa80df8e44641ff193a65a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 30 Jul 2021 11:00:10 +1200 Subject: [PATCH 077/105] Bump version to v1.20.3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 1c1e9eb3a5..d2cc495952 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.2" +__version__ = "1.20.3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 7828f48b9affbc3520bb2f8909a5950cdcfbd7ae Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:20:02 +1000 Subject: [PATCH 078/105] Correctly invert esp32 RMT TX (#2022) --- esphome/components/remote_transmitter/remote_transmitter.h | 1 + .../remote_transmitter/remote_transmitter_esp32.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index 000fbabfee..853b5b6289 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -41,6 +41,7 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, bool initialized_{false}; std::vector rmt_temp_; esp_err_t error_code_{ESP_OK}; + bool inverted_{false}; #endif uint8_t carrier_duty_percent_{50}; }; diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 3d3e26160a..7b366fa52b 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -50,6 +50,7 @@ void RemoteTransmitterComponent::configure_rmt() { } else { c.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW; c.tx_config.idle_level = RMT_IDLE_LEVEL_HIGH; + this->inverted_ = true; } esp_err_t error = rmt_config(&c); @@ -95,10 +96,10 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen val -= item; if (rmt_i % 2 == 0) { - rmt_item.level0 = static_cast(level); + rmt_item.level0 = static_cast(level ^ this->inverted_); rmt_item.duration0 = static_cast(item); } else { - rmt_item.level1 = static_cast(level); + rmt_item.level1 = static_cast(level ^ this->inverted_); rmt_item.duration1 = static_cast(item); this->rmt_temp_.push_back(rmt_item); } From 790d6ef94cad7e69f00e2800f4018c3f8195e060 Mon Sep 17 00:00:00 2001 From: WeekendWarrior1 Date: Mon, 26 Jul 2021 17:32:08 +1000 Subject: [PATCH 079/105] Move configure_rmt() into setup() (#2028) --- .../components/remote_transmitter/remote_transmitter_esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 7b366fa52b..90166d2741 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -9,7 +9,7 @@ namespace remote_transmitter { static const char *const TAG = "remote_transmitter"; -void RemoteTransmitterComponent::setup() {} +void RemoteTransmitterComponent::setup() { this->configure_rmt(); } void RemoteTransmitterComponent::dump_config() { ESP_LOGCONFIG(TAG, "Remote Transmitter..."); From 3ffa59f0cd43becb7d1a5ad1954bec1ac706b217 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 26 Jul 2021 15:19:50 +0200 Subject: [PATCH 080/105] Fix climate restore schema changed resulting in invalid restore (#2068) Co-authored-by: Stefan Agner --- esphome/components/climate/climate.cpp | 6 +++++- esphome/components/climate/climate.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 07347e4eee..8da2206f37 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -312,8 +312,12 @@ void Climate::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +// Random 32bit value; If this changes existing restore preferences are invalidated +static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; + optional Climate::restore_state_() { - this->rtc_ = global_preferences.make_preference(this->get_object_id_hash()); + this->rtc_ = + global_preferences.make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); ClimateDeviceRestoreState recovered{}; if (!this->rtc_.load(&recovered)) return {}; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index ed5c5069b7..690e81c250 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -120,6 +120,7 @@ class ClimateCall { }; /// Struct used to save the state of the climate device in restore memory. +/// Make sure to update RESTORE_STATE_VERSION when changing the struct entries. struct ClimateDeviceRestoreState { ClimateMode mode; bool uses_custom_fan_mode{false}; From 29f0508dc21393b03a877a1439ee3d5b5d3426c6 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 28 Jul 2021 21:23:41 +0200 Subject: [PATCH 081/105] Fix PID climate breaks when restoring old modes (#2086) --- esphome/components/pid/pid_climate.cpp | 13 ++----------- esphome/components/pid/pid_climate.h | 1 - 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index ef8a4df962..4c7d92e26d 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -37,7 +37,7 @@ void PIDClimate::control(const climate::ClimateCall &call) { // If switching to off mode, set output immediately if (this->mode == climate::CLIMATE_MODE_OFF) - this->handle_non_auto_mode_(); + this->write_output_(0.0f); this->publish_state(); } @@ -98,15 +98,6 @@ void PIDClimate::write_output_(float value) { } this->pid_computed_callback_.call(); } -void PIDClimate::handle_non_auto_mode_() { - // in non-auto mode, switch directly to appropriate action - // - OFF mode -> Output at 0% - if (this->mode == climate::CLIMATE_MODE_OFF) { - this->write_output_(0.0); - } else { - assert(false); - } -} void PIDClimate::update_pid_() { float value; if (isnan(this->current_temperature) || isnan(this->target_temperature)) { @@ -135,7 +126,7 @@ void PIDClimate::update_pid_() { } if (this->mode == climate::CLIMATE_MODE_OFF) { - this->handle_non_auto_mode_(); + this->write_output_(0.0); } else { this->write_output_(value); } diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index f11d768867..ff301386b6 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -56,7 +56,6 @@ class PIDClimate : public climate::Climate, public Component { bool supports_heat_() const { return this->heat_output_ != nullptr; } void write_output_(float value); - void handle_non_auto_mode_(); /// The sensor used for getting the current temperature sensor::Sensor *sensor_; From 5ce923ea90d03fceb98e158dadcab5bd0c478b99 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Fri, 30 Jul 2021 01:55:26 -0300 Subject: [PATCH 082/105] fix diplay trigger missing base class (#2099) --- esphome/components/display/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 2dff00da03..947b09a258 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -31,7 +31,9 @@ DisplayPageShowPrevAction = display_ns.class_( DisplayIsDisplayingPageCondition = display_ns.class_( "DisplayIsDisplayingPageCondition", automation.Condition ) -DisplayOnPageChangeTrigger = display_ns.class_("DisplayOnPageChangeTrigger") +DisplayOnPageChangeTrigger = display_ns.class_( + "DisplayOnPageChangeTrigger", automation.Trigger +) CONF_ON_PAGE_CHANGE = "on_page_change" From a6fac2b1750b05a62acb3985556de2df7a063535 Mon Sep 17 00:00:00 2001 From: Paul Monigatti Date: Sat, 31 Jul 2021 23:20:10 +1200 Subject: [PATCH 083/105] Fix min/max keys in MQTT Number to match Home Assistant (#2102) --- esphome/components/mqtt/mqtt_number.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 0311526340..f209f4fe20 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -39,8 +39,8 @@ void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo // https://www.home-assistant.io/integrations/number.mqtt/ if (!traits.get_icon().empty()) root["icon"] = traits.get_icon(); - root["min_value"] = traits.get_min_value(); - root["max_value"] = traits.get_max_value(); + root["min"] = traits.get_min_value(); + root["max"] = traits.get_max_value(); root["step"] = traits.get_step(); config.command_topic = true; From bdbd8134553baf231bd0395d477ec4a74d2c0c65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20=C5=9Aliwi=C5=84ski?= Date: Sat, 31 Jul 2021 14:37:48 +0200 Subject: [PATCH 084/105] Use proper schema for the analog pin shorthand (#2103) The wrong error message is displayed like: > GPIO17 (TOUT) is an analog-only pin on the ESP8266. in case of the analog pin validation because the method `shorthand_analog_pin` uses wrong `GPIO_FULL_INPUT_PIN_SCHEMA` instead of `GPIO_FULL_ANALOG_PIN_SCHEMA`. --- esphome/pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/pins.py b/esphome/pins.py index 6356ae9bd0..e314e3fc30 100644 --- a/esphome/pins.py +++ b/esphome/pins.py @@ -1104,7 +1104,7 @@ def shorthand_input_pullup_pin(value): def shorthand_analog_pin(value): value = analog_pin(value) - return GPIO_FULL_INPUT_PIN_SCHEMA({CONF_NUMBER: value}) + return GPIO_FULL_ANALOG_PIN_SCHEMA({CONF_NUMBER: value}) def validate_has_interrupt(value): From b0d12aeea11f39153cc9b8c0f2b43da64a895c6f Mon Sep 17 00:00:00 2001 From: Carlos Garcia Saura Date: Mon, 2 Aug 2021 10:28:25 +0200 Subject: [PATCH 085/105] [duty_cycle] initialize two missing variables (#2088) --- esphome/components/duty_cycle/duty_cycle_sensor.cpp | 1 + esphome/components/duty_cycle/duty_cycle_sensor.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp index 8b7446b681..c989421948 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp +++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp @@ -13,6 +13,7 @@ void DutyCycleSensor::setup() { this->store_.pin = this->pin_->to_isr(); this->store_.last_level = this->pin_->digital_read(); this->last_update_ = micros(); + this->store_.last_interrupt = micros(); this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, CHANGE); } diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h index 2205bec729..e168f20eff 100644 --- a/esphome/components/duty_cycle/duty_cycle_sensor.h +++ b/esphome/components/duty_cycle/duty_cycle_sensor.h @@ -29,7 +29,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent { protected: GPIOPin *pin_; - DutyCycleSensorStore store_; + DutyCycleSensorStore store_{}; uint32_t last_update_; }; From dd637582a43e0eb297887e36c25287a62db61679 Mon Sep 17 00:00:00 2001 From: brambo123 <52667932+brambo123@users.noreply.github.com> Date: Tue, 3 Aug 2021 19:56:23 +0200 Subject: [PATCH 086/105] Fix time.on_time triggering if time jumped back (#1806) Co-authored-by: Otto winter --- esphome/components/time/automation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/esphome/components/time/automation.cpp b/esphome/components/time/automation.cpp index 6d34459fea..84cc76a762 100644 --- a/esphome/components/time/automation.cpp +++ b/esphome/components/time/automation.cpp @@ -22,7 +22,10 @@ void CronTrigger::loop() { return; if (this->last_check_.has_value()) { - if (*this->last_check_ >= time) { + if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > 900) { + // We went back in time (a lot), probably caused by time synchronization + ESP_LOGW(TAG, "Time has jumped back!"); + } else if (*this->last_check_ >= time) { // already handled this one return; } From eeaba74553d8a5ed629fd5fc6be83ec232eefca4 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 4 Aug 2021 17:33:17 +0200 Subject: [PATCH 087/105] Fix external components not refreshing with default or high refresh time (#2122) --- esphome/components/external_components/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py index 3e833d0b66..8f6e2bece4 100644 --- a/esphome/components/external_components/__init__.py +++ b/esphome/components/external_components/__init__.py @@ -147,7 +147,7 @@ def _process_git_config(config: dict, refresh) -> str: age = datetime.datetime.now() - datetime.datetime.fromtimestamp( file_timestamp.stat().st_mtime ) - if age.seconds > refresh.total_seconds: + if age.total_seconds() > refresh.total_seconds: _LOGGER.info("Updating %s", key) _LOGGER.debug("Location: %s", repo_dir) # Stash local changes (if any) From 532907219baaa3b856c501308a2cbbc7bb38568b Mon Sep 17 00:00:00 2001 From: Otto winter Date: Wed, 4 Aug 2021 17:46:10 +0200 Subject: [PATCH 088/105] Bump version to v1.20.4 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index d2cc495952..6dda61cce2 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.20.3" +__version__ = "1.20.4" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From b0bc898278810d4cc8267ee03dc9df36a0476fa0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:27:39 +1200 Subject: [PATCH 089/105] Bump version to v1.21.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 50bac3ca8d..2b9418a81c 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.22.0-dev" +__version__ = "1.21.0b1" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 6847645782d1e1993fd9bc269d7ec9100d588d00 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:45:04 +1200 Subject: [PATCH 090/105] Fix bad merge conflict --- esphome/components/sensor/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index ebd4fcc26c..26ddac3464 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -258,11 +258,11 @@ def sensor_schema( ): validate_last_reset_type } ) - if last_reset_type_ != LAST_RESET_TYPE_NONE: + if last_reset_type != LAST_RESET_TYPE_NONE: schema = schema.extend( { cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type_ + CONF_LAST_RESET_TYPE, default=last_reset_type ): validate_last_reset_type } ) From 2735f96516ed94fbe10b44c79be21e82d7fbabfe Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 11 Aug 2021 21:54:24 +1200 Subject: [PATCH 091/105] Fix bad merge again --- esphome/components/sensor/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 26ddac3464..19f2a9f0e6 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -258,14 +258,6 @@ def sensor_schema( ): validate_last_reset_type } ) - if last_reset_type != LAST_RESET_TYPE_NONE: - schema = schema.extend( - { - cv.Optional( - CONF_LAST_RESET_TYPE, default=last_reset_type - ): validate_last_reset_type - } - ) return schema From 9efeea14f2560a93ea3d3f0cc3d0cd268f1e0edc Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 15 Aug 2021 21:40:34 +0200 Subject: [PATCH 092/105] Always send all light state values in API (#2150) --- esphome/components/api/api_connection.cpp | 25 ++++++++--------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 5fba549a57..f644ec62e0 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -310,22 +310,15 @@ bool APIConnection::send_light_state(light::LightState *light) { resp.key = light->get_object_id_hash(); resp.state = values.is_on(); resp.color_mode = static_cast(color_mode); - if (color_mode & light::ColorCapability::BRIGHTNESS) - resp.brightness = values.get_brightness(); - if (color_mode & light::ColorCapability::RGB) { - resp.color_brightness = values.get_color_brightness(); - resp.red = values.get_red(); - resp.green = values.get_green(); - resp.blue = values.get_blue(); - } - if (color_mode & light::ColorCapability::WHITE) - resp.white = values.get_white(); - if (color_mode & light::ColorCapability::COLOR_TEMPERATURE) - resp.color_temperature = values.get_color_temperature(); - if (color_mode & light::ColorCapability::COLD_WARM_WHITE) { - resp.cold_white = values.get_cold_white(); - resp.warm_white = values.get_warm_white(); - } + resp.brightness = values.get_brightness(); + resp.color_brightness = values.get_color_brightness(); + resp.red = values.get_red(); + resp.green = values.get_green(); + resp.blue = values.get_blue(); + resp.white = values.get_white(); + resp.color_temperature = values.get_color_temperature(); + resp.cold_white = values.get_cold_white(); + resp.warm_white = values.get_warm_white(); if (light->supports_effects()) resp.effect = light->get_effect_name(); return this->send_light_state_response(resp); From bd457f64d88d717fbf9dba9bca8d7fba131c0a27 Mon Sep 17 00:00:00 2001 From: puuu Date: Mon, 16 Aug 2021 04:59:29 +0900 Subject: [PATCH 093/105] let sensors announce its state_class via mqtt (#2155) --- esphome/components/mqtt/mqtt_sensor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index c106a95902..ce7e89c584 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -61,6 +61,9 @@ void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryCo if (this->sensor_->get_force_update()) root["force_update"] = true; + if (this->sensor_->state_class == sensor::STATE_CLASS_MEASUREMENT) + root["state_class"] = "measurement"; + config.command_topic = false; } bool MQTTSensorComponent::send_initial_state() { From 02b5a3efb8144ad982b722267be6b88a216d74ee Mon Sep 17 00:00:00 2001 From: Keith Burzinski Date: Sun, 15 Aug 2021 16:31:48 -0500 Subject: [PATCH 094/105] Thermostat delayed fan mode fix (#2158) --- .../thermostat/thermostat_climate.cpp | 24 +++++++++++-------- .../thermostat/thermostat_climate.h | 7 ++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index d9d8b106ea..a75713cbb9 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -57,29 +57,34 @@ void ThermostatClimate::refresh() { } bool ThermostatClimate::climate_action_change_delayed() { + bool state_mismatch = this->action != this->compute_action_(true); + switch (this->compute_action_(true)) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - return !this->idle_action_ready_(); + return state_mismatch && (!this->idle_action_ready_()); case climate::CLIMATE_ACTION_COOLING: - return !this->cooling_action_ready_(); + return state_mismatch && (!this->cooling_action_ready_()); case climate::CLIMATE_ACTION_HEATING: - return !this->heating_action_ready_(); + return state_mismatch && (!this->heating_action_ready_()); case climate::CLIMATE_ACTION_FAN: - return !this->fanning_action_ready_(); + return state_mismatch && (!this->fanning_action_ready_()); case climate::CLIMATE_ACTION_DRYING: - return !this->drying_action_ready_(); + return state_mismatch && (!this->drying_action_ready_()); default: break; } return false; } -bool ThermostatClimate::fan_mode_change_delayed() { return !this->fan_mode_ready_(); } +bool ThermostatClimate::fan_mode_change_delayed() { + bool state_mismatch = this->fan_mode.value_or(climate::CLIMATE_FAN_ON) != this->prev_fan_mode_; + return state_mismatch && (!this->fan_mode_ready_()); +} climate::ClimateAction ThermostatClimate::delayed_climate_action() { return this->compute_action_(true); } -climate::ClimateFanMode ThermostatClimate::delayed_fan_mode() { return this->desired_fan_mode_; } +climate::ClimateFanMode ThermostatClimate::locked_fan_mode() { return this->prev_fan_mode_; } bool ThermostatClimate::hysteresis_valid() { if ((this->supports_cool_ || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) && @@ -510,7 +515,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { // already in target mode return; - this->desired_fan_mode_ = fan_mode; // needed for timer callback + this->fan_mode = fan_mode; if (this->fan_mode_ready_()) { Trigger<> *trig = this->fan_mode_auto_trigger_; @@ -564,7 +569,6 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode) { this->start_timer_(thermostat::TIMER_FAN_MODE); assert(trig != nullptr); trig->trigger(); - this->fan_mode = fan_mode; this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } @@ -733,7 +737,7 @@ void ThermostatClimate::cooling_on_timer_callback_() { void ThermostatClimate::fan_mode_timer_callback_() { ESP_LOGVV(TAG, "fan_mode timer expired"); this->timer_[thermostat::TIMER_FAN_MODE].active = false; - this->switch_to_fan_mode_(this->desired_fan_mode_); + this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON)); if (this->supports_fan_only_action_uses_fan_mode_timer_) this->switch_to_action_(this->compute_action_()); } diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index 1a5fd82ac0..60777e7c81 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -136,8 +136,8 @@ class ThermostatClimate : public climate::Climate, public Component { bool fan_mode_change_delayed(); /// Returns the climate action that is being delayed (check climate_action_change_delayed(), first!) climate::ClimateAction delayed_climate_action(); - /// Returns the fan mode that is being delayed (check fan_mode_change_delayed(), first!) - climate::ClimateFanMode delayed_fan_mode(); + /// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!) + climate::ClimateFanMode locked_fan_mode(); /// Set point and hysteresis validation bool hysteresis_valid(); // returns true if valid void validate_target_temperature(); @@ -377,9 +377,6 @@ class ThermostatClimate : public climate::Climate, public Component { Trigger<> *prev_mode_trigger_{nullptr}; Trigger<> *prev_swing_mode_trigger_{nullptr}; - /// Desired fan_mode -- used to store desired mode for callback when switching is delayed - climate::ClimateFanMode desired_fan_mode_{climate::CLIMATE_FAN_ON}; - /// Store previously-known states /// /// These are used to determine when a trigger/action needs to be called From 1be9bac3a97c824e7ad222caebd39b2c570f2d82 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Mon, 16 Aug 2021 01:57:50 +0200 Subject: [PATCH 095/105] Fix native API log level enum values (#2151) --- esphome/components/api/api.proto | 8 ++++---- esphome/components/api/api_connection.cpp | 2 -- esphome/components/api/api_pb2.cpp | 11 ++--------- esphome/components/api/api_pb2.h | 8 ++++---- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index b9b56d3f8e..d21e82f50c 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -555,9 +555,10 @@ enum LogLevel { LOG_LEVEL_ERROR = 1; LOG_LEVEL_WARN = 2; LOG_LEVEL_INFO = 3; - LOG_LEVEL_DEBUG = 4; - LOG_LEVEL_VERBOSE = 5; - LOG_LEVEL_VERY_VERBOSE = 6; + LOG_LEVEL_CONFIG = 4; + LOG_LEVEL_DEBUG = 5; + LOG_LEVEL_VERBOSE = 6; + LOG_LEVEL_VERY_VERBOSE = 7; } message SubscribeLogsRequest { option (id) = 28; @@ -572,7 +573,6 @@ message SubscribeLogsResponse { option (no_delay) = false; LogLevel level = 1; - string tag = 2; string message = 3; bool send_failed = 4; } diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index f644ec62e0..4a31f15e77 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -694,8 +694,6 @@ bool APIConnection::send_log_message(int level, const char *tag, const char *lin auto buffer = this->create_buffer(); // LogLevel level = 1; buffer.encode_uint32(1, static_cast(level)); - // string tag = 2; - // buffer.encode_string(2, tag, strlen(tag)); // string message = 3; buffer.encode_string(3, line, strlen(line)); // SubscribeLogsResponse - 29 diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index eecba7a68e..aaeef57324 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -120,6 +120,8 @@ template<> const char *proto_enum_to_string(enums::LogLevel val return "LOG_LEVEL_WARN"; case enums::LOG_LEVEL_INFO: return "LOG_LEVEL_INFO"; + case enums::LOG_LEVEL_CONFIG: + return "LOG_LEVEL_CONFIG"; case enums::LOG_LEVEL_DEBUG: return "LOG_LEVEL_DEBUG"; case enums::LOG_LEVEL_VERBOSE: @@ -2334,10 +2336,6 @@ bool SubscribeLogsResponse::decode_varint(uint32_t field_id, ProtoVarInt value) } bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { - case 2: { - this->tag = value.as_string(); - return true; - } case 3: { this->message = value.as_string(); return true; @@ -2348,7 +2346,6 @@ bool SubscribeLogsResponse::decode_length(uint32_t field_id, ProtoLengthDelimite } void SubscribeLogsResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_enum(1, this->level); - buffer.encode_string(2, this->tag); buffer.encode_string(3, this->message); buffer.encode_bool(4, this->send_failed); } @@ -2360,10 +2357,6 @@ void SubscribeLogsResponse::dump_to(std::string &out) const { out.append(proto_enum_to_string(this->level)); out.append("\n"); - out.append(" tag: "); - out.append("'").append(this->tag).append("'"); - out.append("\n"); - out.append(" message: "); out.append("'").append(this->message).append("'"); out.append("\n"); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index be32488391..2395a4bec3 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -58,9 +58,10 @@ enum LogLevel : uint32_t { LOG_LEVEL_ERROR = 1, LOG_LEVEL_WARN = 2, LOG_LEVEL_INFO = 3, - LOG_LEVEL_DEBUG = 4, - LOG_LEVEL_VERBOSE = 5, - LOG_LEVEL_VERY_VERBOSE = 6, + LOG_LEVEL_CONFIG = 4, + LOG_LEVEL_DEBUG = 5, + LOG_LEVEL_VERBOSE = 6, + LOG_LEVEL_VERY_VERBOSE = 7, }; enum ServiceArgType : uint32_t { SERVICE_ARG_TYPE_BOOL = 0, @@ -627,7 +628,6 @@ class SubscribeLogsRequest : public ProtoMessage { class SubscribeLogsResponse : public ProtoMessage { public: enums::LogLevel level{}; - std::string tag{}; std::string message{}; bool send_failed{false}; void encode(ProtoWriteBuffer buffer) const override; From 14e04eb2313f0c4f6daefd6d8a428f13e41a97b7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 16 Aug 2021 12:32:48 +1200 Subject: [PATCH 096/105] Bump version to v1.21.0b2 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 2b9418a81c..89be4b7fd7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b1" +__version__ = "1.21.0b2" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 9ee3463d07ed0243a6bb827dc7dbd1115ec4bb1f Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Tue, 17 Aug 2021 04:02:38 +0200 Subject: [PATCH 097/105] Initialize color temperature to value within range if possible (#2168) --- esphome/components/light/light_state.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 278229fbd1..030cf4b7a2 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -39,6 +39,13 @@ void LightState::setup() { effect->init_internal(this); } + // When supported color temperature range is known, initialize color temperature setting within bounds. + float min_mireds = this->get_traits().get_min_mireds(); + if (min_mireds > 0) { + this->remote_values.set_color_temperature(min_mireds); + this->current_values.set_color_temperature(min_mireds); + } + auto call = this->make_call(); LightStateRTCState recovered{}; switch (this->restore_mode_) { From cbdb96f1058e33201972b97d9b16e0180c539512 Mon Sep 17 00:00:00 2001 From: Daniel Hyles Date: Tue, 17 Aug 2021 18:05:37 +1000 Subject: [PATCH 098/105] Add a dummy color temp (#2161) --- esphome/components/hbridge/hbridge_light_output.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/hbridge/hbridge_light_output.h b/esphome/components/hbridge/hbridge_light_output.h index 2f4f87134c..c309154852 100644 --- a/esphome/components/hbridge/hbridge_light_output.h +++ b/esphome/components/hbridge/hbridge_light_output.h @@ -19,6 +19,8 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput { light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE}); + traits.set_min_mireds(153); + traits.set_max_mireds(500); return traits; } From 44bb5a89c8ee5756bea37f1a425ba6d87120cf8f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 17 Aug 2021 11:43:51 +0200 Subject: [PATCH 099/105] Add Gas device class to DSMR component (#2169) --- esphome/components/dsmr/sensor.py | 5 +++-- esphome/components/sensor/__init__.py | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 05c568e21a..84b263c2d5 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -5,6 +5,7 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, ICON_EMPTY, @@ -178,7 +179,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), @@ -186,7 +187,7 @@ CONFIG_SCHEMA = cv.Schema( "m³", ICON_EMPTY, 3, - DEVICE_CLASS_EMPTY, + DEVICE_CLASS_GAS, STATE_CLASS_MEASUREMENT, LAST_RESET_TYPE_NEVER, ), diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 19f2a9f0e6..390cfcf551 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -40,6 +40,7 @@ from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, @@ -62,6 +63,7 @@ DEVICE_CLASSES = [ DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, + DEVICE_CLASS_GAS, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MONETARY, From 9c605f2d46571a327bab46d4e9b4a3e9f62c8eb4 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:22:00 +1200 Subject: [PATCH 100/105] Send dirty states when screen wakes up (#2167) --- esphome/components/nextion/nextion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 14c7b34a4c..3f44fe4075 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -543,6 +543,7 @@ void Nextion::process_nextion_commands_() { ESP_LOGVV(TAG, "Received Nextion leaves sleep automatically"); this->is_sleeping_ = false; this->wake_callback_.call(); + this->all_components_send_state_(false); break; } case 0x88: // system successful start up From 61a9c9fa3327499437349311e9a9c35bf355c7cf Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:32:19 +1200 Subject: [PATCH 101/105] Remove specified accuracy_decimals from total_daily_energy (#2174) --- esphome/components/total_daily_energy/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index ec38daaf57..6e75feae48 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -20,7 +20,6 @@ TotalDailyEnergy = total_daily_energy_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( - accuracy_decimals=0, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_MEASUREMENT, last_reset_type=LAST_RESET_TYPE_AUTO, From 4e3b95d120afc0c0d247131ae6db27531e480628 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:04:13 +1200 Subject: [PATCH 102/105] Add new total_increasing state-class for Home Assistant 2021.9+ (#2166) --- esphome/components/api/api.proto | 1 + esphome/components/api/api_pb2.cpp | 2 ++ esphome/components/api/api_pb2.h | 1 + esphome/components/sensor/__init__.py | 1 + esphome/components/sensor/sensor.cpp | 4 ++++ esphome/components/sensor/sensor.h | 1 + esphome/const.py | 3 +++ 7 files changed, 13 insertions(+) diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index d21e82f50c..e3ef2d7c9e 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -448,6 +448,7 @@ message LightCommandRequest { enum SensorStateClass { STATE_CLASS_NONE = 0; STATE_CLASS_MEASUREMENT = 1; + STATE_CLASS_TOTAL_INCREASING = 2; } enum SensorLastResetType { diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index aaeef57324..f5860bee64 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -94,6 +94,8 @@ template<> const char *proto_enum_to_string(enums::Sens return "STATE_CLASS_NONE"; case enums::STATE_CLASS_MEASUREMENT: return "STATE_CLASS_MEASUREMENT"; + case enums::STATE_CLASS_TOTAL_INCREASING: + return "STATE_CLASS_TOTAL_INCREASING"; default: return "UNKNOWN"; } diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index 2395a4bec3..93bfcd9b55 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -47,6 +47,7 @@ enum ColorMode : uint32_t { enum SensorStateClass : uint32_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; enum SensorLastResetType : uint32_t { LAST_RESET_NONE = 0, diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 390cfcf551..1bb4e25a17 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -81,6 +81,7 @@ StateClasses = sensor_ns.enum("StateClass") STATE_CLASSES = { "": StateClasses.STATE_CLASS_NONE, "measurement": StateClasses.STATE_CLASS_MEASUREMENT, + "total_increasing": StateClasses.STATE_CLASS_TOTAL_INCREASING, } validate_state_class = cv.enum(STATE_CLASSES, lower=True, space="_") diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 6e8765a8df..1a5c76db51 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -10,6 +10,8 @@ const char *state_class_to_string(StateClass state_class) { switch (state_class) { case STATE_CLASS_MEASUREMENT: return "measurement"; + case STATE_CLASS_TOTAL_INCREASING: + return "total_increasing"; case STATE_CLASS_NONE: default: return ""; @@ -72,6 +74,8 @@ void Sensor::set_state_class(StateClass state_class) { this->state_class = state void Sensor::set_state_class(const std::string &state_class) { if (str_equals_case_insensitive(state_class, "measurement")) { this->state_class = STATE_CLASS_MEASUREMENT; + } else if (str_equals_case_insensitive(state_class, "total_increasing")) { + this->state_class = STATE_CLASS_TOTAL_INCREASING; } else { ESP_LOGW(TAG, "'%s' - Unrecognized state class %s", this->get_name().c_str(), state_class.c_str()); } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index b9908b6cbe..f0d7ba4887 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -37,6 +37,7 @@ namespace sensor { enum StateClass : uint8_t { STATE_CLASS_NONE = 0, STATE_CLASS_MEASUREMENT = 1, + STATE_CLASS_TOTAL_INCREASING = 2, }; const char *state_class_to_string(StateClass state_class); diff --git a/esphome/const.py b/esphome/const.py index 89be4b7fd7..010ea1290a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -840,6 +840,9 @@ STATE_CLASS_NONE = "" # The state represents a measurement in present time STATE_CLASS_MEASUREMENT = "measurement" +# The state represents a total that only increases, a decrease is considered a reset. +STATE_CLASS_TOTAL_INCREASING = "total_increasing" + # This sensor does not support resetting. ie, it is not accumulative LAST_RESET_TYPE_NONE = "" # This sensor is expected to never reset its value From 409d4b9d475c6abf142a451fe0a740dfe6c853df Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 11:11:39 +1200 Subject: [PATCH 103/105] Bump version to v1.21.0b3 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 010ea1290a..a17152a64b 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b2" +__version__ = "1.21.0b3" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From d4c2a85f9c79ba7aebc9d99aed584ad54460f7a7 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 14:25:10 +1200 Subject: [PATCH 104/105] Bump version to v2021.8.0 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index a17152a64b..e25ba7e046 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "1.21.0b3" +__version__ = "2021.8.0" ESP_PLATFORM_ESP32 = "ESP32" ESP_PLATFORM_ESP8266 = "ESP8266" From 2e59ad90cc0ef374c7360e0bde1c9ce44d271363 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 18 Aug 2021 15:10:44 +1200 Subject: [PATCH 105/105] Fix docker release for new tags without `v` --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b15b540f6..3dc4952422 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,7 @@ jobs: id: tag run: | if [[ "$GITHUB_EVENT_NAME" = "release" ]]; then - TAG="${GITHUB_REF#refs/tags/v}" + TAG="${GITHUB_REF#refs/tags/}" else TAG=$(cat esphome/const.py | sed -n -E "s/^__version__\s+=\s+\"(.+)\"$/\1/p") today="$(date --utc '+%Y%m%d')" @@ -138,7 +138,7 @@ jobs: - env: TOKEN: ${{ secrets.DEPLOY_HASSIO_TOKEN }} run: | - TAG="${GITHUB_REF#refs/tags/v}" + TAG="${GITHUB_REF#refs/tags/}" curl \ -u ":$TOKEN" \ -X POST \