Home Assistant Git Exporter
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import socket
|
||||
import binascii
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
class APSystemsInvalidData(Exception):
|
||||
pass
|
||||
|
||||
class APSystemsSocket:
|
||||
def __init__(self, ipaddr, nographs, port=8899, raw_ecu=None, raw_inverter=None):
|
||||
global no_graphs
|
||||
no_graphs = nographs
|
||||
self.ipaddr = ipaddr
|
||||
self.port = port
|
||||
|
||||
# what do we expect socket data to end in
|
||||
self.recv_suffix = b'END\n'
|
||||
|
||||
# how long to wait on socket commands until we get our recv_suffix
|
||||
self.timeout = 10
|
||||
|
||||
# how big of a buffer to read at a time from the socket
|
||||
# https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues/108
|
||||
self.recv_size = 1024
|
||||
|
||||
# how long to wait between socket open/closes
|
||||
self.socket_sleep_time = 5
|
||||
|
||||
self.cmd_suffix = "END\n"
|
||||
self.ecu_query = "APS1100160001" + self.cmd_suffix
|
||||
self.inverter_query_prefix = "APS1100280002"
|
||||
self.inverter_query_suffix = self.cmd_suffix
|
||||
self.inverter_signal_prefix = "APS1100280030"
|
||||
self.inverter_signal_suffix = self.cmd_suffix
|
||||
self.ecu_id = None
|
||||
self.qty_of_inverters = 0
|
||||
self.qty_of_online_inverters = 0
|
||||
self.lifetime_energy = 0
|
||||
self.current_power = 0
|
||||
self.today_energy = 0
|
||||
self.inverters = {}
|
||||
self.firmware = None
|
||||
self.timezone = None
|
||||
self.last_update = None
|
||||
self.vsl = 0
|
||||
self.tsl = 0
|
||||
self.ecu_raw_data = raw_ecu
|
||||
self.inverter_raw_data = raw_inverter
|
||||
self.inverter_raw_signal = None
|
||||
self.read_buffer = b''
|
||||
self.socket = None
|
||||
self.socket_open = False
|
||||
self.errors = []
|
||||
|
||||
def send_read_from_socket(self, cmd):
|
||||
try:
|
||||
self.sock.settimeout(self.timeout)
|
||||
self.sock.sendall(cmd.encode('utf-8'))
|
||||
time.sleep(self.socket_sleep_time)
|
||||
self.read_buffer = b''
|
||||
self.sock.settimeout(self.timeout)
|
||||
# An infinite loop was causing the integration to block
|
||||
# https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues/115
|
||||
# Solution might cause a new issue when large solar array's applies
|
||||
self.read_buffer = self.sock.recv(self.recv_size)
|
||||
return self.read_buffer
|
||||
except Exception as err:
|
||||
self.close_socket()
|
||||
raise APSystemsInvalidData(err)
|
||||
|
||||
def close_socket(self):
|
||||
try:
|
||||
if self.socket_open:
|
||||
self.sock.shutdown(socket.SHUT_RDWR)
|
||||
self.sock.close()
|
||||
self.socket_open = False
|
||||
except Exception as err:
|
||||
raise APSystemsInvalidData(err)
|
||||
|
||||
def open_socket(self):
|
||||
self.socket_open = False
|
||||
try:
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.sock.settimeout(self.timeout)
|
||||
self.sock.connect((self.ipaddr, self.port))
|
||||
self.socket_open = True
|
||||
except Exception as err:
|
||||
raise APSystemsInvalidData(err)
|
||||
|
||||
def query_ecu(self):
|
||||
#read ECU data
|
||||
self.open_socket()
|
||||
self.ecu_raw_data = self.send_read_from_socket(self.ecu_query)
|
||||
self.close_socket()
|
||||
try:
|
||||
self.process_ecu_data()
|
||||
except Exception as err:
|
||||
raise APSystemsInvalidData(err)
|
||||
|
||||
#read inverter data
|
||||
# Some ECUs like the socket to be closed and re-opened between commands
|
||||
self.open_socket()
|
||||
cmd = self.inverter_query_prefix + self.ecu_id + self.inverter_query_suffix
|
||||
self.inverter_raw_data = self.send_read_from_socket(cmd)
|
||||
self.close_socket()
|
||||
|
||||
#read signal data
|
||||
# Some ECUs like the socket to be closed and re-opened between commands
|
||||
self.open_socket()
|
||||
cmd = self.inverter_signal_prefix + self.ecu_id + self.inverter_signal_suffix
|
||||
self.inverter_raw_signal = self.send_read_from_socket(cmd)
|
||||
self.close_socket()
|
||||
|
||||
data = self.process_inverter_data()
|
||||
data["ecu_id"] = self.ecu_id
|
||||
if self.lifetime_energy != 0:
|
||||
data["lifetime_energy"] = self.lifetime_energy
|
||||
data["current_power"] = self.current_power
|
||||
# apply filter for ECU-R-pro firmware bug where both are zero
|
||||
if self.qty_of_inverters > 0:
|
||||
data["qty_of_inverters"] = self.qty_of_inverters
|
||||
data["today_energy"] = self.today_energy
|
||||
data["qty_of_online_inverters"] = self.qty_of_online_inverters
|
||||
return(data)
|
||||
|
||||
def aps_int_from_bytes(self, codec: bytes, start: int, length: int) -> int:
|
||||
try:
|
||||
return int (binascii.b2a_hex(codec[(start):(start+length)]), 16)
|
||||
except ValueError as err:
|
||||
debugdata = binascii.b2a_hex(codec)
|
||||
error = f"Unable to convert binary to int with length={length} at location={start} with data={debugdata}"
|
||||
raise APSystemsInvalidData(error)
|
||||
|
||||
def aps_uid(self, codec, start):
|
||||
return str(binascii.b2a_hex(codec[(start):(start+12)]))[2:14]
|
||||
|
||||
def aps_str(self, codec, start, amount):
|
||||
return str(codec[start:(start+amount)])[2:(amount+2)]
|
||||
|
||||
def aps_datetimestamp(self, codec, start, amount):
|
||||
timestr=str(binascii.b2a_hex(codec[start:(start+amount)]))[2:(amount+2)]
|
||||
return timestr[0:4]+"-"+timestr[4:6]+"-"+timestr[6:8]+" "+timestr[8:10]+":"+timestr[10:12]+":"+timestr[12:14]
|
||||
|
||||
def check_ecu_checksum(self, data, cmd):
|
||||
datalen = len(data) - 1
|
||||
try:
|
||||
checksum = int(data[5:9])
|
||||
except ValueError as err:
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
error = f"could not extract checksum int from '{cmd}' data={debugdata}"
|
||||
raise APSystemsInvalidData(error)
|
||||
|
||||
if datalen != checksum:
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
error = f"Checksum on '{cmd}' failed checksum={checksum} datalen={datalen} data={debugdata}"
|
||||
raise APSystemsInvalidData(error)
|
||||
|
||||
start_str = self.aps_str(data, 0, 3)
|
||||
end_str = self.aps_str(data, len(data) - 4, 3)
|
||||
|
||||
if start_str != 'APS':
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
error = f"Result on '{cmd}' incorrect start signature '{start_str}' != APS data={debugdata}"
|
||||
raise APSystemsInvalidData(error)
|
||||
|
||||
if end_str != 'END':
|
||||
debugdata = binascii.b2a_hex(data)
|
||||
error = f"Result on '{cmd}' incorrect end signature '{end_str}' != END data={debugdata}"
|
||||
raise APSystemsInvalidData(error)
|
||||
|
||||
return True
|
||||
|
||||
def process_ecu_data(self, data=None):
|
||||
if self.ecu_raw_data != '' and (self.aps_str(self.ecu_raw_data,9,4)) == '0001':
|
||||
data = self.ecu_raw_data
|
||||
_LOGGER.debug(binascii.b2a_hex(data))
|
||||
self.check_ecu_checksum(data, "ECU Query")
|
||||
self.ecu_id = self.aps_str(data, 13, 12)
|
||||
self.lifetime_energy = self.aps_int_from_bytes(data, 27, 4) / 10
|
||||
self.current_power = self.aps_int_from_bytes(data, 31, 4)
|
||||
self.today_energy = self.aps_int_from_bytes(data, 35, 4) / 100
|
||||
if self.aps_str(data,25,2) == "01":
|
||||
self.qty_of_inverters = self.aps_int_from_bytes(data, 46, 2)
|
||||
self.qty_of_online_inverters = self.aps_int_from_bytes(data, 48, 2)
|
||||
self.vsl = int(self.aps_str(data, 52, 3))
|
||||
self.firmware = self.aps_str(data, 55, self.vsl)
|
||||
self.tsl = int(self.aps_str(data, 55 + self.vsl, 3))
|
||||
self.timezone = self.aps_str(data, 58 + self.vsl, self.tsl)
|
||||
elif self.aps_str(data,25,2) == "02":
|
||||
self.qty_of_inverters = self.aps_int_from_bytes(data, 39, 2)
|
||||
self.qty_of_online_inverters = self.aps_int_from_bytes(data, 41, 2)
|
||||
self.vsl = int(self.aps_str(data, 49, 3))
|
||||
self.firmware = self.aps_str(data, 52, self.vsl)
|
||||
|
||||
def process_signal_data(self, data=None):
|
||||
signal_data = {}
|
||||
if self.inverter_raw_signal != '' and (self.aps_str(self.inverter_raw_signal,9,4)) == '0030':
|
||||
data = self.inverter_raw_signal
|
||||
_LOGGER.debug(binascii.b2a_hex(data))
|
||||
self.check_ecu_checksum(data, "Signal Query")
|
||||
if not self.qty_of_inverters:
|
||||
return signal_data
|
||||
location = 15
|
||||
for i in range(0, self.qty_of_inverters):
|
||||
uid = self.aps_uid(data, location)
|
||||
location += 6
|
||||
strength = data[location]
|
||||
location += 1
|
||||
strength = int((strength / 255) * 100)
|
||||
signal_data[uid] = strength
|
||||
return signal_data
|
||||
|
||||
def process_inverter_data(self, data=None):
|
||||
output = {}
|
||||
if self.inverter_raw_data != '' and (self.aps_str(self.inverter_raw_data,9,4)) == '0002':
|
||||
data = self.inverter_raw_data
|
||||
_LOGGER.debug(binascii.b2a_hex(data))
|
||||
self.check_ecu_checksum(data, "Inverter data")
|
||||
istr = ''
|
||||
cnt1 = 0
|
||||
cnt2 = 26
|
||||
if self.aps_str(data, 14, 2) == '00':
|
||||
timestamp = self.aps_datetimestamp(data, 19, 14)
|
||||
inverter_qty = self.aps_int_from_bytes(data, 17, 2)
|
||||
self.last_update = timestamp
|
||||
output["timestamp"] = timestamp
|
||||
output["inverters"] = {}
|
||||
signal = self.process_signal_data()
|
||||
inverters = {}
|
||||
|
||||
while cnt1 < inverter_qty:
|
||||
inv={}
|
||||
if self.aps_str(data, 15, 2) == '01':
|
||||
inverter_uid = self.aps_uid(data, cnt2)
|
||||
inv["uid"] = inverter_uid
|
||||
inv["online"] = bool(self.aps_int_from_bytes(data, cnt2 + 6, 1))
|
||||
istr = self.aps_str(data, cnt2 + 7, 2)
|
||||
|
||||
# Should graphs be updated?
|
||||
if inv["online"] == False and no_graphs == True:
|
||||
inv["signal"] = None
|
||||
else:
|
||||
inv["signal"] = signal.get(inverter_uid, 0)
|
||||
|
||||
# Distinguishes the different inverters from this point down
|
||||
if istr in [ '01', '04', '05']:
|
||||
power = []
|
||||
voltages = []
|
||||
|
||||
# Should graphs be updated?
|
||||
if inv["online"] == True:
|
||||
inv["temperature"] = self.aps_int_from_bytes(data, cnt2 + 11, 2) - 100
|
||||
if inv["online"] == False and no_graphs == True:
|
||||
inv["frequency"] = None
|
||||
power.append(None)
|
||||
voltages.append(None)
|
||||
power.append(None)
|
||||
voltages.append(None)
|
||||
else:
|
||||
inv["frequency"] = self.aps_int_from_bytes(data, cnt2 + 9, 2) / 10
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 13, 2))
|
||||
voltages.append(self.aps_int_from_bytes(data, cnt2 + 15, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 17, 2))
|
||||
voltages.append(self.aps_int_from_bytes(data, cnt2 + 19, 2))
|
||||
|
||||
inv_details = {
|
||||
"model" : "YC600/DS3 series",
|
||||
"channel_qty" : 2,
|
||||
"power" : power,
|
||||
"voltage" : voltages
|
||||
}
|
||||
inv.update(inv_details)
|
||||
cnt2 = cnt2 + 21
|
||||
elif istr == '02':
|
||||
power = []
|
||||
voltages = []
|
||||
|
||||
# Should graphs be updated?
|
||||
if inv["online"]:
|
||||
inv["temperature"] = self.aps_int_from_bytes(data, cnt2 + 11, 2) - 100
|
||||
if inv["online"] == False and no_graphs == True:
|
||||
inv["frequency"] = None
|
||||
power.append(None)
|
||||
voltages.append(None)
|
||||
power.append(None)
|
||||
voltages.append(None)
|
||||
power.append(None)
|
||||
voltages.append(None)
|
||||
power.append(None)
|
||||
else:
|
||||
inv["frequency"] = self.aps_int_from_bytes(data, cnt2 + 9, 2) / 10
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 13, 2))
|
||||
voltages.append(self.aps_int_from_bytes(data, cnt2 + 15, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 17, 2))
|
||||
voltages.append(self.aps_int_from_bytes(data, cnt2 + 19, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 21, 2))
|
||||
voltages.append(self.aps_int_from_bytes(data, cnt2 + 23, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 25, 2))
|
||||
|
||||
inv_details = {
|
||||
"model" : "YC1000/QT2",
|
||||
"channel_qty" : 4,
|
||||
"power" : power,
|
||||
"voltage" : voltages
|
||||
}
|
||||
inv.update(inv_details)
|
||||
cnt2 = cnt2 + 27
|
||||
elif istr == '03':
|
||||
power = []
|
||||
voltages = []
|
||||
|
||||
# Should graphs be updated?
|
||||
if inv["online"]:
|
||||
inv["temperature"] = self.aps_int_from_bytes(data, cnt2 + 11, 2) - 100
|
||||
if inv["online"] == False and no_graphs == True:
|
||||
inv["frequency"] = None
|
||||
power.append(None)
|
||||
voltages.append(None)
|
||||
power.append(None)
|
||||
power.append(None)
|
||||
power.append(None)
|
||||
else:
|
||||
inv["frequency"] = self.aps_int_from_bytes(data, cnt2 + 9, 2) / 10
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 13, 2))
|
||||
voltages.append(self.aps_int_from_bytes(data, cnt2 + 15, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 17, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 19, 2))
|
||||
power.append(self.aps_int_from_bytes(data, cnt2 + 21, 2))
|
||||
|
||||
inv_details = {
|
||||
"model" : "QS1",
|
||||
"channel_qty" : 4,
|
||||
"power" : power,
|
||||
"voltage" : voltages
|
||||
}
|
||||
inv.update(inv_details)
|
||||
cnt2 = cnt2 + 23
|
||||
else:
|
||||
cnt2 = cnt2 + 9
|
||||
inverters[inverter_uid] = inv
|
||||
cnt1 = cnt1 + 1
|
||||
self.inverters = inverters
|
||||
output["inverters"] = inverters
|
||||
return (output)
|
||||
@@ -0,0 +1,241 @@
|
||||
import logging
|
||||
import requests
|
||||
|
||||
import voluptuous as vol
|
||||
import traceback
|
||||
import datetime as dt
|
||||
from datetime import timedelta
|
||||
|
||||
from .APSystemsSocket import APSystemsSocket, APSystemsInvalidData
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.discovery import load_platform
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant import config_entries, exceptions
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.components.persistent_notification import (
|
||||
create as create_persistent_notification
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
DataUpdateCoordinator,
|
||||
UpdateFailed,
|
||||
)
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
PLATFORMS = [ "sensor", "binary_sensor", "switch" ]
|
||||
|
||||
class WiFiSet():
|
||||
ipaddr = ""
|
||||
ssid = ""
|
||||
wpa = ""
|
||||
cache = 3
|
||||
WiFiSet = WiFiSet()
|
||||
|
||||
# handle all the communications with the ECUR class and deal with our need for caching, etc
|
||||
class ECUR():
|
||||
def __init__(self, ipaddr, ssid, wpa, cache, nographs):
|
||||
self.ecu = APSystemsSocket(ipaddr, nographs)
|
||||
self.cache_count = 0
|
||||
self.data_from_cache = False
|
||||
self.querying = True
|
||||
self.inverters_online = True
|
||||
self.ecu_restarting = False
|
||||
self.cached_data = {}
|
||||
WiFiSet.ipaddr = ipaddr
|
||||
WiFiSet.ssid = ssid
|
||||
WiFiSet.wpa = wpa
|
||||
WiFiSet.cache = cache
|
||||
|
||||
def stop_query(self):
|
||||
self.querying = False
|
||||
|
||||
def start_query(self):
|
||||
self.querying = True
|
||||
|
||||
def inverters_off(self):
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||
url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_off'
|
||||
try:
|
||||
get_url = requests.post(url, headers=headers)
|
||||
self.inverters_online = False
|
||||
_LOGGER.debug(f"Response from ECU on switching the inverters off: {str(get_url.status_code)}")
|
||||
except Exception as err:
|
||||
_LOGGER.warning(f"Attempt to switch inverters off failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")
|
||||
|
||||
def inverters_on(self):
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||
url = 'http://'+ str(WiFiSet.ipaddr) + '/index.php/configuration/set_switch_all_on'
|
||||
try:
|
||||
get_url = requests.post(url, headers=headers)
|
||||
self.inverters_online = True
|
||||
_LOGGER.debug(f"Response from ECU on switching the inverters on: {str(get_url.status_code)}")
|
||||
except Exception as err:
|
||||
_LOGGER.warning(f"Attempt to switch inverters on failed with error: {err} (This switch is only compatible with ECU-R pro and ECU-C type ECU's)")
|
||||
|
||||
def use_cached_data(self, msg):
|
||||
# we got invalid data, so we need to pull from cache
|
||||
self.error_msg = msg
|
||||
self.cache_count += 1
|
||||
self.data_from_cache = True
|
||||
|
||||
if self.cache_count == WiFiSet.cache:
|
||||
_LOGGER.warning(f"Communication with the ECU failed after {WiFiSet.cache} repeated attempts.")
|
||||
data = {'SSID': WiFiSet.ssid, 'channel': 0, 'method': 2, 'psk_wep': '', 'psk_wpa': WiFiSet.wpa}
|
||||
_LOGGER.debug(f"Data sent with URL: {data}")
|
||||
# Determine ECU type to decide ECU restart (for ECU-C and ECU-R with sunspec only)
|
||||
if (self.cached_data.get("ecu_id", None)[0:3] == "215") or (self.cached_data.get("ecu_id", None)[0:4] == "2162"):
|
||||
url = 'http://' + str(WiFiSet.ipaddr) + '/index.php/management/set_wlan_ap'
|
||||
headers = {'X-Requested-With': 'XMLHttpRequest'}
|
||||
try:
|
||||
get_url = requests.post(url, headers=headers, data=data)
|
||||
_LOGGER.debug(f"Response from ECU on restart: {str(get_url.status_code)}")
|
||||
self.ecu_restarting = True
|
||||
except Exception as err:
|
||||
_LOGGER.warning(f"Attempt to restart ECU failed with error: {err}. Querying is stopped automatically.")
|
||||
self.querying = False
|
||||
else:
|
||||
# Older ECU-R models starting with 2160
|
||||
_LOGGER.warning("Try manually power cycling the ECU. Querying is stopped automatically, turn switch back on after restart of ECU.")
|
||||
self.querying = False
|
||||
|
||||
if self.cached_data.get("ecu_id", None) == None:
|
||||
_LOGGER.debug(f"Cached data {self.cached_data}")
|
||||
raise UpdateFailed(f"Unable to get correct data from ECU, and no cached data. See log for details, and try power cycling the ECU.")
|
||||
return self.cached_data
|
||||
|
||||
def update(self):
|
||||
data = {}
|
||||
# if we aren't actively quering data, pull data form the cache
|
||||
# this is so we can stop querying after sunset
|
||||
if not self.querying:
|
||||
_LOGGER.debug("Not querying ECU due to query=False")
|
||||
data = self.cached_data
|
||||
self.data_from_cache = True
|
||||
data["data_from_cache"] = self.data_from_cache
|
||||
data["querying"] = self.querying
|
||||
return self.cached_data
|
||||
|
||||
_LOGGER.debug("Querying ECU...")
|
||||
try:
|
||||
data = self.ecu.query_ecu()
|
||||
_LOGGER.debug("Got data from ECU")
|
||||
|
||||
# we got good results, so we store it and set flags about our cache state
|
||||
if data["ecu_id"] != None:
|
||||
self.cached_data = data
|
||||
self.cache_count = 0
|
||||
self.data_from_cache = False
|
||||
self.ecu_restarting = False
|
||||
self.error_message = ""
|
||||
else:
|
||||
msg = f"Using cached data from last successful communication from ECU. Error: no ecu_id returned"
|
||||
_LOGGER.warning(msg)
|
||||
data = self.use_cached_data(msg)
|
||||
|
||||
except APSystemsInvalidData as err:
|
||||
msg = f"Using cached data from last successful communication from ECU. Invalid data error: {err}"
|
||||
if str(err) != 'timed out':
|
||||
_LOGGER.warning(msg)
|
||||
data = self.use_cached_data(msg)
|
||||
|
||||
except Exception as err:
|
||||
msg = f"Using cached data from last successful communication from ECU. Exception error: {err}"
|
||||
_LOGGER.warning(msg)
|
||||
data = self.use_cached_data(msg)
|
||||
|
||||
data["data_from_cache"] = self.data_from_cache
|
||||
data["querying"] = self.querying
|
||||
data["restart_ecu"] = self.ecu_restarting
|
||||
_LOGGER.debug(f"Returning {data}")
|
||||
if data.get("ecu_id", None) == None:
|
||||
raise UpdateFailed(f"Somehow data doesn't contain a valid ecu_id")
|
||||
return data
|
||||
|
||||
async def update_listener(hass, config):
|
||||
# Handle options update being triggered by config entry options updates
|
||||
_LOGGER.debug(f"Configuration updated: {config.as_dict()}")
|
||||
ecu = ECUR(config.data["host"],
|
||||
config.data["SSID"],
|
||||
config.data["WPA-PSK"],
|
||||
config.data["CACHE"],
|
||||
config.data["stop_graphs"]
|
||||
)
|
||||
|
||||
async def async_setup_entry(hass, config):
|
||||
# Setup the APsystems platform """
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
host = config.data["host"]
|
||||
interval = timedelta(seconds=config.data["scan_interval"])
|
||||
# Defaults for new parameters that might not have been set yet from previous integration versions
|
||||
cache = config.data.get("CACHE", 5)
|
||||
ssid = config.data.get("SSID", "ECU-WiFi_SSID")
|
||||
wpa = config.data.get("WPA-PSK", "myWiFipassword")
|
||||
nographs = config.data.get("stop_graphs", False)
|
||||
ecu = ECUR(host, ssid, wpa, cache, nographs)
|
||||
|
||||
|
||||
async def do_ecu_update():
|
||||
return await hass.async_add_executor_job(ecu.update)
|
||||
|
||||
coordinator = DataUpdateCoordinator(
|
||||
hass,
|
||||
_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_method=do_ecu_update,
|
||||
update_interval=interval,
|
||||
)
|
||||
|
||||
hass.data[DOMAIN] = {
|
||||
"ecu" : ecu,
|
||||
"coordinator" : coordinator
|
||||
}
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config.entry_id,
|
||||
identifiers={(DOMAIN, f"ecu_{ecu.ecu.ecu_id}")},
|
||||
manufacturer="APSystems",
|
||||
suggested_area="Roof",
|
||||
name=f"ECU {ecu.ecu.ecu_id}",
|
||||
model=ecu.ecu.firmware,
|
||||
sw_version=ecu.ecu.firmware,
|
||||
)
|
||||
|
||||
inverters = coordinator.data.get("inverters", {})
|
||||
for uid,inv_data in inverters.items():
|
||||
model = inv_data.get("model", "Inverter")
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config.entry_id,
|
||||
identifiers={(DOMAIN, f"inverter_{uid}")},
|
||||
manufacturer="APSystems",
|
||||
suggested_area="Roof",
|
||||
name=f"Inverter {uid}",
|
||||
model=inv_data.get("model")
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(config, PLATFORMS)
|
||||
config.async_on_unload(config.add_update_listener(update_listener))
|
||||
return True
|
||||
|
||||
async def async_remove_config_entry_device(hass, config, device_entry) -> bool:
|
||||
if device_entry is not None:
|
||||
# Notify the user that the device has been removed
|
||||
create_persistent_notification(
|
||||
hass,
|
||||
title="Important notification",
|
||||
message=f"The following device was removed from the system: {device_entry}"
|
||||
)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def async_unload_entry(hass, config):
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config, PLATFORMS)
|
||||
coordinator = hass.data[DOMAIN].get("coordinator")
|
||||
ecu = hass.data[DOMAIN].get("ecu")
|
||||
ecu.stop_query()
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(config.entry_id)
|
||||
return unload_ok
|
||||
@@ -0,0 +1,92 @@
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
)
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
RELOAD_ICON,
|
||||
CACHE_ICON,
|
||||
RESTART_ICON
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_setup_entry(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
ecu = hass.data[DOMAIN].get("ecu")
|
||||
coordinator = hass.data[DOMAIN].get("coordinator")
|
||||
|
||||
sensors = [
|
||||
APSystemsECUBinarySensor(coordinator, ecu, "data_from_cache",
|
||||
label="Using Cached Data", icon=CACHE_ICON),
|
||||
APSystemsECUBinarySensor(coordinator, ecu, "restart_ecu",
|
||||
label="Restart", icon=RESTART_ICON)
|
||||
]
|
||||
add_entities(sensors)
|
||||
|
||||
|
||||
class APSystemsECUBinarySensor(CoordinatorEntity, BinarySensorEntity):
|
||||
|
||||
def __init__(self, coordinator, ecu, field, label=None, devclass=None, icon=None):
|
||||
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.coordinator = coordinator
|
||||
|
||||
self._ecu = ecu
|
||||
self._field = field
|
||||
self._label = label
|
||||
if not label:
|
||||
self._label = field
|
||||
self._icon = icon
|
||||
|
||||
self._name = f"ECU {self._label}"
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
return f"{self._ecu.ecu.ecu_id}_{self._field}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self.coordinator.data.get(self._field)
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
|
||||
attrs = {
|
||||
"ecu_id" : self._ecu.ecu.ecu_id,
|
||||
"firmware" : self._ecu.ecu.firmware,
|
||||
"timezone" : self._ecu.ecu.timezone,
|
||||
"last_update" : self._ecu.ecu.last_update
|
||||
}
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def entity_category(self):
|
||||
return EntityCategory.DIAGNOSTIC
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
parent = f"ecu_{self._ecu.ecu.ecu_id}"
|
||||
return {
|
||||
"identifiers": {
|
||||
(DOMAIN, parent),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
import traceback
|
||||
from datetime import timedelta
|
||||
from homeassistant.core import callback
|
||||
from .APSystemsSocket import APSystemsSocket, APSystemsInvalidData
|
||||
from homeassistant import config_entries, exceptions
|
||||
from homeassistant.const import CONF_HOST, CONF_SCAN_INTERVAL
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
from .const import DOMAIN, CONF_SSID, CONF_WPA_PSK, CONF_CACHE, CONF_STOP_GRAPHS
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str,
|
||||
vol.Required(CONF_SCAN_INTERVAL, default=300): int,
|
||||
vol.Optional(CONF_CACHE, default=5): int,
|
||||
vol.Optional(CONF_SSID, default="ECU-WIFI_local"): str,
|
||||
vol.Optional(CONF_WPA_PSK, default="default"): str,
|
||||
vol.Optional(CONF_STOP_GRAPHS, default=False): bool,
|
||||
})
|
||||
|
||||
@config_entries.HANDLERS.register(DOMAIN)
|
||||
class APSsystemsFlowHandler(config_entries.ConfigFlow):
|
||||
|
||||
VERSION = 1
|
||||
def __init__(self):
|
||||
_LOGGER.debug("Starting config flow class...")
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
_LOGGER.debug("Starting user step")
|
||||
errors = {}
|
||||
if user_input is None:
|
||||
_LOGGER.debug("Show form because user input is empty")
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
_LOGGER.debug("User input is not empty, processing input")
|
||||
try:
|
||||
_LOGGER.debug("Initial attempt to query ECU")
|
||||
ap_ecu = APSystemsSocket(user_input["host"], user_input["stop_graphs"])
|
||||
test_query = await self.hass.async_add_executor_job(ap_ecu.query_ecu)
|
||||
ecu_id = test_query.get("ecu_id", None)
|
||||
if ecu_id != None:
|
||||
return self.async_create_entry(title=f"ECU: {ecu_id}", data=user_input)
|
||||
else:
|
||||
errors["host"] = "no_ecuid"
|
||||
except APSystemsInvalidData as err:
|
||||
_LOGGER.exception(f"APSystemsInvalidData exception: {err}")
|
||||
errors["host"] = "cannot_connect"
|
||||
except Exception as err:
|
||||
_LOGGER.exception(f"Unknown error occurred during setup: {err}")
|
||||
errors["host"] = "unknown"
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
_LOGGER.debug("get options flow")
|
||||
return APSsystemsOptionsFlowHandler(config_entry)
|
||||
|
||||
class APSsystemsOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
_LOGGER.debug("Starting options flow step class")
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, user_input=None):
|
||||
errors = {}
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
errors=errors,
|
||||
data_schema=vol.Schema({
|
||||
vol.Required(CONF_HOST, default=self.config_entry.data.get(CONF_HOST)): str,
|
||||
vol.Optional(CONF_SCAN_INTERVAL, default=300,
|
||||
description={"suggested_value": self.config_entry.data.get(CONF_SCAN_INTERVAL)}): int,
|
||||
vol.Optional(CONF_CACHE, default=5,
|
||||
description={"suggested_value": self.config_entry.data.get(CONF_CACHE)}): int,
|
||||
vol.Optional(CONF_SSID, default="ECU-WiFi_SSID",
|
||||
description={"suggested_value": self.config_entry.data.get(CONF_SSID)}): str,
|
||||
vol.Optional(CONF_WPA_PSK, default="myWiFipassword",
|
||||
description={"suggested_value": self.config_entry.data.get(CONF_WPA_PSK)}): str,
|
||||
vol.Optional(CONF_STOP_GRAPHS, default=self.config_entry.data.get(CONF_STOP_GRAPHS)): bool
|
||||
})
|
||||
)
|
||||
try:
|
||||
ap_ecu = APSystemsSocket(user_input["host"], user_input["stop_graphs"])
|
||||
_LOGGER.debug("Attempt to query ECU")
|
||||
test_query = await self.hass.async_add_executor_job(ap_ecu.query_ecu)
|
||||
ecu_id = test_query.get("ecu_id", None)
|
||||
if ecu_id != None:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self.config_entry, data=user_input, options=self.config_entry.options
|
||||
)
|
||||
coordinator = self.hass.data[DOMAIN].get("coordinator")
|
||||
coordinator.update_interval = timedelta(seconds=self.config_entry.data.get(CONF_SCAN_INTERVAL))
|
||||
return self.async_create_entry(title=f"ECU: {ecu_id}", data={})
|
||||
else:
|
||||
errors["host"] = "no_ecuid"
|
||||
except APSystemsInvalidData as err:
|
||||
errors["host"] = "cannot_connect"
|
||||
except Exception as err:
|
||||
_LOGGER.debug(f"Unknown error occurred during setup: {err}")
|
||||
errors["host"] = "unknown"
|
||||
@@ -0,0 +1,13 @@
|
||||
DOMAIN = 'apsystems_ecur'
|
||||
SOLAR_ICON = "mdi:solar-power"
|
||||
FREQ_ICON = "mdi:sine-wave"
|
||||
SIGNAL_ICON = "mdi:signal"
|
||||
RELOAD_ICON = "mdi:reload"
|
||||
CACHE_ICON = "mdi:cached"
|
||||
RESTART_ICON = "mdi:restart"
|
||||
POWER_ICON = "mdi:power"
|
||||
|
||||
CONF_SSID = "SSID"
|
||||
CONF_WPA_PSK = "WPA-PSK"
|
||||
CONF_CACHE = "CACHE"
|
||||
CONF_STOP_GRAPHS = "stop_graphs"
|
||||
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
TO_REDACT = {CONF_TOKEN}
|
||||
|
||||
from .const import (
|
||||
DOMAIN
|
||||
)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_get_device_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, device: DeviceEntry
|
||||
) -> dict:
|
||||
"""Return diagnostics for a config entry."""
|
||||
|
||||
_LOGGER.debug("Diagnostics being called")
|
||||
|
||||
ecu = hass.data[DOMAIN].get("ecu")
|
||||
_LOGGER.debug(f"Diagnostics being called {ecu}")
|
||||
|
||||
diag_data = {"entry": async_redact_data(ecu.ecu.dump_data(), TO_REDACT)}
|
||||
|
||||
return diag_data
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"domain": "apsystems_ecur",
|
||||
"name": "APSystems PV solar ECU",
|
||||
"codeowners": ["@ksheumaker"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"documentation": "https://github.com/ksheumaker/homeassistant-apsystems_ecur",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues",
|
||||
"loggers": ["custom_components.apsystems_ecur"],
|
||||
"requirements": [],
|
||||
"version": "v1.4.3"
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
from datetime import timedelta, datetime, date
|
||||
import logging
|
||||
|
||||
import async_timeout
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity,
|
||||
)
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorStateClass,
|
||||
)
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
SOLAR_ICON,
|
||||
FREQ_ICON,
|
||||
SIGNAL_ICON
|
||||
)
|
||||
|
||||
from homeassistant.const import (
|
||||
UnitOfPower,
|
||||
UnitOfEnergy,
|
||||
UnitOfTemperature,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfFrequency,
|
||||
PERCENTAGE
|
||||
)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_setup_entry(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
ecu = hass.data[DOMAIN].get("ecu")
|
||||
coordinator = hass.data[DOMAIN].get("coordinator")
|
||||
|
||||
sensors = [
|
||||
APSystemsECUSensor(coordinator, ecu, "current_power",
|
||||
label="Current Power",
|
||||
unit=UnitOfPower.WATT,
|
||||
devclass=SensorDeviceClass.POWER,
|
||||
icon=SOLAR_ICON,
|
||||
stateclass=SensorStateClass.MEASUREMENT
|
||||
),
|
||||
APSystemsECUSensor(coordinator, ecu, "today_energy",
|
||||
label="Today Energy",
|
||||
unit=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
devclass=SensorDeviceClass.ENERGY,
|
||||
icon=SOLAR_ICON,
|
||||
stateclass=SensorStateClass.TOTAL_INCREASING
|
||||
),
|
||||
APSystemsECUSensor(coordinator, ecu, "lifetime_energy",
|
||||
label="Lifetime Energy",
|
||||
unit=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
devclass=SensorDeviceClass.ENERGY,
|
||||
icon=SOLAR_ICON,
|
||||
stateclass=SensorStateClass.TOTAL_INCREASING
|
||||
),
|
||||
APSystemsECUSensor(coordinator, ecu, "qty_of_inverters",
|
||||
label="Inverters",
|
||||
icon=SOLAR_ICON,
|
||||
entity_category=EntityCategory.DIAGNOSTIC
|
||||
),
|
||||
APSystemsECUSensor(coordinator, ecu, "qty_of_online_inverters",
|
||||
label="Inverters Online",
|
||||
icon=SOLAR_ICON,
|
||||
entity_category=EntityCategory.DIAGNOSTIC
|
||||
),
|
||||
]
|
||||
|
||||
inverters = coordinator.data.get("inverters", {})
|
||||
for uid,inv_data in inverters.items():
|
||||
_LOGGER.debug(f"Inverter {uid} {inv_data.get('channel_qty')}")
|
||||
# https://github.com/ksheumaker/homeassistant-apsystems_ecur/issues/110
|
||||
if inv_data.get("channel_qty") != None:
|
||||
sensors.extend([
|
||||
APSystemsECUInverterSensor(coordinator, ecu, uid, "temperature",
|
||||
label="Temperature",
|
||||
unit=UnitOfTemperature.CELSIUS,
|
||||
devclass=SensorDeviceClass.TEMPERATURE,
|
||||
stateclass=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC
|
||||
),
|
||||
APSystemsECUInverterSensor(coordinator, ecu, uid, "frequency",
|
||||
label="Frequency",
|
||||
unit=UnitOfFrequency.HERTZ,
|
||||
stateclass=SensorStateClass.MEASUREMENT,
|
||||
devclass=SensorDeviceClass.FREQUENCY,
|
||||
icon=FREQ_ICON,
|
||||
entity_category=EntityCategory.DIAGNOSTIC
|
||||
),
|
||||
APSystemsECUInverterSensor(coordinator, ecu, uid, "voltage",
|
||||
label="Voltage",
|
||||
unit=UnitOfElectricPotential.VOLT,
|
||||
stateclass=SensorStateClass.MEASUREMENT,
|
||||
devclass=SensorDeviceClass.VOLTAGE, entity_category=EntityCategory.DIAGNOSTIC
|
||||
),
|
||||
APSystemsECUInverterSensor(coordinator, ecu, uid, "signal",
|
||||
label="Signal",
|
||||
unit=PERCENTAGE,
|
||||
stateclass=SensorStateClass.MEASUREMENT,
|
||||
devclass=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
icon=SIGNAL_ICON,
|
||||
entity_category=EntityCategory.DIAGNOSTIC
|
||||
)
|
||||
])
|
||||
for i in range(0, inv_data.get("channel_qty", 0)):
|
||||
sensors.append(
|
||||
APSystemsECUInverterSensor(coordinator, ecu, uid, f"power",
|
||||
index=i, label=f"Power Ch {i+1}",
|
||||
unit=UnitOfPower.WATT,
|
||||
devclass=SensorDeviceClass.POWER,
|
||||
icon=SOLAR_ICON,
|
||||
stateclass=SensorStateClass.MEASUREMENT
|
||||
)
|
||||
)
|
||||
add_entities(sensors)
|
||||
|
||||
|
||||
class APSystemsECUInverterSensor(CoordinatorEntity, SensorEntity):
|
||||
def __init__(self, coordinator, ecu, uid, field, index=0, label=None, icon=None, unit=None, devclass=None, stateclass=None, entity_category=None):
|
||||
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.coordinator = coordinator
|
||||
|
||||
self._index = index
|
||||
self._uid = uid
|
||||
self._ecu = ecu
|
||||
self._field = field
|
||||
self._devclass = devclass
|
||||
self._label = label
|
||||
if not label:
|
||||
self._label = field
|
||||
self._icon = icon
|
||||
self._unit = unit
|
||||
self._stateclass = stateclass
|
||||
self._entity_category = entity_category
|
||||
|
||||
self._name = f"Inverter {self._uid} {self._label}"
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
field = self._field
|
||||
if self._index != None:
|
||||
field = f"{field}_{self._index}"
|
||||
return f"{self._ecu.ecu.ecu_id}_{self._uid}_{field}"
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
return self._devclass
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
_LOGGER.debug(f"State called for {self._field}")
|
||||
if self._field == "voltage":
|
||||
return self.coordinator.data.get("inverters", {}).get(self._uid, {}).get("voltage", [])[0]
|
||||
elif self._field == "power":
|
||||
_LOGGER.debug(f"POWER {self._uid} {self._index}")
|
||||
return self.coordinator.data.get("inverters", {}).get(self._uid, {}).get("power", [])[self._index]
|
||||
else:
|
||||
return self.coordinator.data.get("inverters", {}).get(self._uid, {}).get(self._field)
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return self._unit
|
||||
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
attrs = {
|
||||
"ecu_id" : self._ecu.ecu.ecu_id,
|
||||
"inverter_uid" : self._uid,
|
||||
"last_update" : self._ecu.ecu.last_update,
|
||||
}
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def state_class(self):
|
||||
_LOGGER.debug(f"State class {self._stateclass} - {self._field}")
|
||||
return self._stateclass
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
parent = f"inverter_{self._uid}"
|
||||
return {
|
||||
"identifiers": {
|
||||
(DOMAIN, parent),
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def entity_category(self):
|
||||
return self._entity_category
|
||||
|
||||
class APSystemsECUSensor(CoordinatorEntity, SensorEntity):
|
||||
|
||||
def __init__(self, coordinator, ecu, field, label=None, icon=None, unit=None, devclass=None, stateclass=None, entity_category=None):
|
||||
|
||||
super().__init__(coordinator)
|
||||
|
||||
self.coordinator = coordinator
|
||||
|
||||
self._ecu = ecu
|
||||
self._field = field
|
||||
self._label = label
|
||||
if not label:
|
||||
self._label = field
|
||||
self._icon = icon
|
||||
self._unit = unit
|
||||
self._devclass = devclass
|
||||
self._stateclass = stateclass
|
||||
self._entity_category = entity_category
|
||||
|
||||
self._name = f"ECU {self._label}"
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
return f"{self._ecu.ecu.ecu_id}_{self._field}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
return self._devclass
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
_LOGGER.debug(f"State called for {self._field}")
|
||||
return self.coordinator.data.get(self._field)
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
return self._unit
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
|
||||
attrs = {
|
||||
"ecu_id" : self._ecu.ecu.ecu_id,
|
||||
"Firmware" : self._ecu.ecu.firmware,
|
||||
"Timezone" : self._ecu.ecu.timezone,
|
||||
"last_update" : self._ecu.ecu.last_update
|
||||
}
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def state_class(self):
|
||||
_LOGGER.debug(f"State class {self._stateclass} - {self._field}")
|
||||
return self._stateclass
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
parent = f"ecu_{self._ecu.ecu.ecu_id}"
|
||||
return {
|
||||
"identifiers": {
|
||||
(DOMAIN, parent),
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def entity_category(self):
|
||||
return self._entity_category
|
||||
@@ -0,0 +1,132 @@
|
||||
import logging
|
||||
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.helpers.entity import EntityCategory
|
||||
from homeassistant.helpers.update_coordinator import (
|
||||
CoordinatorEntity
|
||||
)
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
RELOAD_ICON,
|
||||
POWER_ICON
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
async def async_setup_entry(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
ecu = hass.data[DOMAIN].get("ecu")
|
||||
coordinator = hass.data[DOMAIN].get("coordinator")
|
||||
switches = [
|
||||
APSystemsECUQuerySwitch(coordinator, ecu, "query_device",
|
||||
label="Query Device", icon=RELOAD_ICON),
|
||||
APSystemsECUInvertersSwitch(coordinator, ecu, "inverters_online",
|
||||
label="Inverters Online", icon=POWER_ICON),
|
||||
]
|
||||
add_entities(switches)
|
||||
|
||||
class APSystemsECUQuerySwitch(CoordinatorEntity, SwitchEntity):
|
||||
def __init__(self, coordinator, ecu, field, label=None, icon=None):
|
||||
super().__init__(coordinator)
|
||||
self.coordinator = coordinator
|
||||
self._ecu = ecu
|
||||
self._field = field
|
||||
self._label = label
|
||||
if not label:
|
||||
self._label = field
|
||||
self._icon = icon
|
||||
self._name = f"ECU {self._label}"
|
||||
self._state = True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
return f"{self._ecu.ecu.ecu_id}_{self._field}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
parent = f"ecu_{self._ecu.ecu.ecu_id}"
|
||||
return {
|
||||
"identifiers": {
|
||||
(DOMAIN, parent),
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def entity_category(self):
|
||||
return EntityCategory.CONFIG
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self._ecu.querying
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
self._ecu.stop_query()
|
||||
self._state = False
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
self._ecu.start_query()
|
||||
self._state = True
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
class APSystemsECUInvertersSwitch(CoordinatorEntity, SwitchEntity):
|
||||
def __init__(self, coordinator, ecu, field, label=None, icon=None):
|
||||
super().__init__(coordinator)
|
||||
self.coordinator = coordinator
|
||||
self._ecu = ecu
|
||||
self._field = field
|
||||
self._label = label
|
||||
if not label:
|
||||
self._label = field
|
||||
self._icon = icon
|
||||
self._name = f"ECU {self._label}"
|
||||
self._state = True
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
return f"{self._ecu.ecu.ecu_id}_{self._field}"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
return self._icon
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
parent = f"ecu_{self._ecu.ecu.ecu_id}"
|
||||
return {
|
||||
"identifiers": {
|
||||
(DOMAIN, parent),
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def entity_category(self):
|
||||
return EntityCategory.CONFIG
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
return self._ecu.inverters_online
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
self._ecu.inverters_off()
|
||||
self._state = False
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
self._ecu.inverters_on()
|
||||
self._state = True
|
||||
self.schedule_update_ha_state()
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "ECU IP-Adresse (bitte Verbindungstabelle in der readme prüfen).",
|
||||
"scan_interval": "ECU Abfrage Frequenz in Sekunden (minimum 300 empfohlen).",
|
||||
"CACHE": "Wiederholungen, wenn ECU ausfällt (Bereich zwischen 1 - 5 empfohlen)",
|
||||
"SSID": "SSID angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
|
||||
"WPA-PSK": "Kennwort angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
|
||||
"stop_graphs": "Aktualisieren die Diagramme nicht, wenn die Wechselrichter offline sind"
|
||||
},
|
||||
"title": "APsystems ECU Konfiguration"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Es wurde keine ECU unter dieser IP-Adresse gefunden oder life-time energy ist Null.",
|
||||
"no_ecuid": "Es wurde keine ECU ID von der ECU zurückgegeben.",
|
||||
"unknown": "Unbekannter Fehler, bitte Logs überprüfen."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "ECU IP-Adresse (bitte Verbindungstabelle in der readme prüfen).",
|
||||
"scan_interval": "ECU Abfrage Frequenz in Sekunden (minimum 300 empfohlen).",
|
||||
"CACHE": "Wiederholungen, wenn ECU ausfällt (Bereich zwischen 1 - 5 empfohlen)",
|
||||
"SSID": "SSID angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
|
||||
"WPA-PSK": "Kennwort angeben (nur für Modelle ECU-R (sunspec) und ECU-C)",
|
||||
"stop_graphs": "Aktualisieren die Diagramme nicht, wenn die Wechselrichter offline sind"
|
||||
},
|
||||
"title": "APsystems ECU Optionen"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Es wurde keine ECU unter dieser IP-Adresse gefunden oder life-time energy ist Null.",
|
||||
"no_ecuid": "Es wurde keine ECU ID von der ECU zurückgegeben.",
|
||||
"unknown": "Unbekannter Fehler, bitte Logs überprüfen."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "ECU IP address (follow connection method table in readme)",
|
||||
"scan_interval": "ECU query interval in seconds (minimum 300 recommended)",
|
||||
"CACHE": "Retries when ECU fails (range between 1 - 5 recommended)",
|
||||
"SSID": "Specify SSID (For ECU-R (sunspec) and ECU-C models only)",
|
||||
"WPA-PSK": "Specify password (For ECU-R (sunspec) and ECU-C models only)",
|
||||
"stop_graphs": "Do not update graphs when inverters are offline"
|
||||
},
|
||||
"title": "APsystems ECU Config"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Can't find ECU at this IP-Address or life-time energy is zero",
|
||||
"no_ecuid": "No ECU ID returned from ECU",
|
||||
"unknown": "Unknown error, see log for details"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "ECU IP address (follow connection method table in readme)",
|
||||
"scan_interval": "ECU query interval in seconds (minimum 300 recommended)",
|
||||
"CACHE": "Retries when ECU fails (range between 1 - 5 recommended)",
|
||||
"SSID": "Specify SSID (For ECU-R (sunspec) and ECU-C models only)",
|
||||
"WPA-PSK": "Specify password (For ECU-R (sunspec) and ECU-C models only)",
|
||||
"stop_graphs": "Do not update graphs when inverters are offline"
|
||||
},
|
||||
"title": "APsystems ECU Options"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Can't find ECU at this IP-Address or life-time energy is zero",
|
||||
"no_ecuid": "No ECU ID returned from ECU",
|
||||
"unknown": "Unknown error, see log for details"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Dirección IP de la ECU (Sigue el método para conectarse en la tabla del archivo readme)",
|
||||
"scan_interval": "Intervalo de conexión a la ECU en segundos (Mínimo 300 recomendado)",
|
||||
"CACHE": "Reintentos cuando la ECU falla (Rango entre 1 - 5 recomendado)",
|
||||
"SSID": "Introduce SSID (Solo para modelos ECU-R (sunspec) and ECU-C)",
|
||||
"WPA-PSK": "Introduce contraseña (Solo para modelos ECU-R (sunspec) and ECU-C)",
|
||||
"stop_graphs": "No actualice los gráficos cuando los inversores estén fuera de línea"
|
||||
},
|
||||
"title": "Configuración APsystems ECU"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "No puedo encontrar la ECU en esta dirección IP o la energía life-time es cero",
|
||||
"no_ecuid": "La ECU no ha devuelto ningún ECU ID",
|
||||
"unknown": "Error desconocido, lee el log para mas detalles"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Dirección IP de la ECU (Sigue el método para conectarse en la tabla del archivo readme)",
|
||||
"scan_interval": "Intervalo de conexión a la ECU en segundos (Mínimo 300 recomendado)",
|
||||
"CACHE": "Reintentos cuando la ECU falla (Rango entre 1 - 5 recomendado)",
|
||||
"SSID": "Introduce SSID (Solo para modelos ECU-R (sunspec) and ECU-C)",
|
||||
"WPA-PSK": "Introduce contraseña (Solo para modelos ECU-R (sunspec) and ECU-C)",
|
||||
"stop_graphs": "No actualice los gráficos cuando los inversores estén fuera de línea"
|
||||
},
|
||||
"title": "Configuración APsystems ECU"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "No puedo encontrar la ECU en esta dirección IP o la energía life-time es cero",
|
||||
"no_ecuid": "La ECU no ha devuelto ningún ECU ID",
|
||||
"unknown": "Error desconocido, lee el log para mas detalles"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Adresse IP ECU (Voir “Prerequisites” dans le fichier Readme)",
|
||||
"scan_interval": "Intervalle des requêtes sur l'ECU en secondes (Min de 300 recommandées)",
|
||||
"CACHE": "Nombre de tentatives en cas d'échec de communication (1 à 5 recommandée)",
|
||||
"SSID": "Spécifier le SSID (Pour ECU-R (Sunspec) et ECU-C seulement)",
|
||||
"WPA-PSK": "Spécifier le mot de passe (Pour ECU-R (Sunspec) et ECU-C seulement)",
|
||||
"stop_graphs": "Ne pas mettre à jour les graphiques lorsque les onduleurs sont hors ligne"
|
||||
},
|
||||
"title": "Configuration ECU APsystems"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Ne trouve pas d'ECU à cette adresse IP ou énergie totale produite nulle",
|
||||
"no_ecuid": "Pas d'ID ECU retourné pour cet ECU",
|
||||
"unknown": "Erreur inconnue, veuillez consulter le journal des logs pour plus de détails"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Adresse IP ECU (Voir “Prerequisites” dans le fichier Readme)",
|
||||
"scan_interval": "Intervalle des requêtes sur l'ECU en secondes (Min de 300 recommandées)",
|
||||
"CACHE": "Nombre de tentatives en cas d'échec de communication (1 à 5 recommandée)",
|
||||
"SSID": "Spécifier le SSID (Pour ECU-R (Sunspec) et ECU-C seulement)",
|
||||
"WPA-PSK": "Spécifier le mot de passe (Pour ECU-R (Sunspec) et ECU-C seulement)",
|
||||
"stop_graphs": "Ne pas mettre à jour les graphiques lorsque les onduleurs sont hors ligne"
|
||||
},
|
||||
"title": "Options ECU APsystems"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Ne trouve pas d'ECU à cette adresse IP ou énergie totale produite nulle",
|
||||
"inverter": "Type d'onduleur inconnu, veuillez vérifier les journaux",
|
||||
"no_ecuid": "Pas d'ID ECU retourné pour cet ECU",
|
||||
"unknown": "Erreur inconnue, veuillez consulter le journal des logs pour plus de détails"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "ECU IP-adres (volg de connectie methode tabel in de readme)",
|
||||
"scan_interval": "ECU query interval in seconden (minimum 300 aanbevolen)",
|
||||
"CACHE": "Pogingen als de ECU niet reageert (tussen 1 - 5 aanbevolen)",
|
||||
"SSID": "Specificeer SSID (Alleen voor ECU-R (sunspec) en ECU-C modellen)",
|
||||
"WPA-PSK": "Specificeer wachtwoord (voor ECU-R (sunspec) en ECU-C modellen)",
|
||||
"stop_graphs": "Werk grafieken niet bij als de omvormers offline zijn"
|
||||
},
|
||||
"title": "APsystems ECU Configuratie"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Kan de ECU niet vinden op dit IP-adres of life-time energy is nul",
|
||||
"no_ecuid": "Geen ECU ID ontvangen",
|
||||
"unknown": "Onbekende fout, zie het log for details"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "ECU IP-adres (volg de connectie methode tabel in de readme)",
|
||||
"scan_interval": "ECU query interval in seconden (minimum 300 aanbevolen)",
|
||||
"CACHE": "Pogingen als de ECU niet reageert (tussen 1 - 5 aanbevolen)",
|
||||
"SSID": "Specificeer SSID (Alleen voor ECU-R (sunspec) en ECU-C modellen)",
|
||||
"WPA-PSK": "Specificeer wachtwoord (voor ECU-R (sunspec) en ECU-C modellen)",
|
||||
"stop_graphs": "Werk grafieken niet bij als de omvormers offline zijn"
|
||||
},
|
||||
"title": "APsystems ECU Opties"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Kan de ECU niet vinden op dit IP-adres of life-time energy is nul",
|
||||
"no_ecuid": "Geen ECU ID ontvangen",
|
||||
"unknown": "Onbekende fout, zie het log voor details"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# Copyright (c) 2019-2022, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
|
||||
# Creative Commons BY-NC-SA 4.0 International Public License
|
||||
# (see LICENSE.md or https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
"""
|
||||
The Average Sensor.
|
||||
|
||||
For more details about this sensor, please refer to the documentation at
|
||||
https://github.com/Limych/ha-average/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.const import SERVICE_RELOAD
|
||||
from homeassistant.core import HomeAssistant, ServiceCall
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.helpers.reload import async_reload_integration_platforms
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DOMAIN, PLATFORMS, STARTUP_MESSAGE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the platforms."""
|
||||
# Print startup message
|
||||
_LOGGER.info(STARTUP_MESSAGE)
|
||||
|
||||
# await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
|
||||
async def reload_service_handler(service: ServiceCall) -> None:
|
||||
"""Reload all average sensors from config."""
|
||||
print("+++++++++++++++++++++++++")
|
||||
print(component)
|
||||
# print(hass.data[DATA_INSTANCES]["sensor"].entities[0])
|
||||
|
||||
await async_reload_integration_platforms(hass, DOMAIN, PLATFORMS)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=vol.Schema({})
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -0,0 +1,65 @@
|
||||
"""The Average Sensor.
|
||||
|
||||
For more details about this sensor, please refer to the documentation at
|
||||
https://github.com/Limych/ha-average/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
from typing import Final
|
||||
|
||||
# Base component constants
|
||||
from homeassistant.const import Platform
|
||||
|
||||
NAME: Final = "Average Sensor"
|
||||
DOMAIN: Final = "average"
|
||||
VERSION: Final = "2.3.4"
|
||||
ISSUE_URL: Final = "https://github.com/Limych/ha-average/issues"
|
||||
|
||||
STARTUP_MESSAGE: Final = f"""
|
||||
-------------------------------------------------------------------
|
||||
{NAME}
|
||||
Version: {VERSION}
|
||||
This is a custom integration!
|
||||
If you have ANY issues with this you need to open an issue here:
|
||||
{ISSUE_URL}
|
||||
-------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
||||
# Configuration and options
|
||||
CONF_START: Final = "start"
|
||||
CONF_END: Final = "end"
|
||||
CONF_DURATION: Final = "duration"
|
||||
CONF_PRECISION: Final = "precision"
|
||||
CONF_PERIOD_KEYS: Final = [CONF_START, CONF_END, CONF_DURATION]
|
||||
CONF_PROCESS_UNDEF_AS: Final = "process_undef_as"
|
||||
|
||||
# Defaults
|
||||
DEFAULT_NAME: Final = "Average"
|
||||
DEFAULT_PRECISION: Final = 2
|
||||
|
||||
# Attributes
|
||||
ATTR_START: Final = "start"
|
||||
ATTR_END: Final = "end"
|
||||
ATTR_SOURCES: Final = "sources"
|
||||
ATTR_COUNT_SOURCES: Final = "count_sources"
|
||||
ATTR_AVAILABLE_SOURCES: Final = "available_sources"
|
||||
ATTR_COUNT: Final = "count"
|
||||
ATTR_MIN_VALUE: Final = "min_value"
|
||||
ATTR_MAX_VALUE: Final = "max_value"
|
||||
#
|
||||
ATTR_TO_PROPERTY: Final = [
|
||||
ATTR_START,
|
||||
ATTR_END,
|
||||
ATTR_SOURCES,
|
||||
ATTR_COUNT_SOURCES,
|
||||
ATTR_AVAILABLE_SOURCES,
|
||||
ATTR_COUNT,
|
||||
ATTR_MAX_VALUE,
|
||||
ATTR_MIN_VALUE,
|
||||
]
|
||||
|
||||
|
||||
UPDATE_MIN_TIME: Final = timedelta(seconds=20)
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"domain": "average",
|
||||
"name": "Average Sensor",
|
||||
"after_dependencies": [
|
||||
"history",
|
||||
"recorder",
|
||||
"weather"
|
||||
],
|
||||
"codeowners": [
|
||||
"@Limych"
|
||||
],
|
||||
"config_flow": false,
|
||||
"dependencies": [],
|
||||
"documentation": "https://github.com/Limych/ha-average",
|
||||
"iot_class": "calculated",
|
||||
"issue_tracker": "https://github.com/Limych/ha-average/issues",
|
||||
"requirements": [],
|
||||
"version": "2.3.4"
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
# Copyright (c) 2019-2022, Andrey "Limych" Khrolenok <andrey@khrolenok.ru>
|
||||
# Creative Commons BY-NC-SA 4.0 International Public License
|
||||
# (see LICENSE.md or https://creativecommons.org/licenses/by-nc-sa/4.0/)
|
||||
|
||||
"""The Average Sensor.
|
||||
|
||||
For more details about this sensor, please refer to the documentation at
|
||||
https://github.com/Limych/ha-average/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import datetime
|
||||
import logging
|
||||
import math
|
||||
import numbers
|
||||
from typing import Any, Optional
|
||||
|
||||
from _sha1 import sha1
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.components.group import expand_entity_ids
|
||||
from homeassistant.components.recorder import get_instance, history
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN
|
||||
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ICON,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_ENTITIES,
|
||||
CONF_NAME,
|
||||
CONF_UNIQUE_ID,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
from homeassistant.util import Throttle
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.util.unit_conversion import TemperatureConverter
|
||||
from homeassistant.util.unit_system import TEMPERATURE_UNITS
|
||||
|
||||
from .const import (
|
||||
ATTR_AVAILABLE_SOURCES,
|
||||
ATTR_COUNT,
|
||||
ATTR_COUNT_SOURCES,
|
||||
ATTR_END,
|
||||
ATTR_MAX_VALUE,
|
||||
ATTR_MIN_VALUE,
|
||||
ATTR_START,
|
||||
ATTR_TO_PROPERTY,
|
||||
CONF_DURATION,
|
||||
CONF_END,
|
||||
CONF_PERIOD_KEYS,
|
||||
CONF_PRECISION,
|
||||
CONF_PROCESS_UNDEF_AS,
|
||||
CONF_START,
|
||||
DEFAULT_NAME,
|
||||
DEFAULT_PRECISION,
|
||||
UPDATE_MIN_TIME,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def check_period_keys(conf):
|
||||
"""Ensure maximum 2 of CONF_PERIOD_KEYS are provided."""
|
||||
count = sum(param in conf for param in CONF_PERIOD_KEYS)
|
||||
if (count == 1 and CONF_DURATION not in conf) or count > 2:
|
||||
raise vol.Invalid(
|
||||
"You must provide none, only "
|
||||
+ CONF_DURATION
|
||||
+ " or maximum 2 of the following: "
|
||||
", ".join(CONF_PERIOD_KEYS)
|
||||
)
|
||||
return conf
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ENTITIES): cv.entity_ids,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_START): cv.template,
|
||||
vol.Optional(CONF_END): cv.template,
|
||||
vol.Optional(CONF_DURATION): cv.positive_time_period,
|
||||
vol.Optional(CONF_PRECISION, default=DEFAULT_PRECISION): int,
|
||||
vol.Optional(CONF_PROCESS_UNDEF_AS): vol.Any(int, float),
|
||||
}
|
||||
),
|
||||
check_period_keys,
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant, config, async_add_entities, discovery_info=None
|
||||
):
|
||||
"""Set up platform."""
|
||||
start = config.get(CONF_START)
|
||||
end = config.get(CONF_END)
|
||||
|
||||
for template in [start, end]:
|
||||
if template is not None:
|
||||
template.hass = hass
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
AverageSensor(
|
||||
hass,
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
config.get(CONF_NAME),
|
||||
start,
|
||||
end,
|
||||
config.get(CONF_DURATION),
|
||||
config.get(CONF_ENTITIES),
|
||||
config.get(CONF_PRECISION),
|
||||
config.get(CONF_PROCESS_UNDEF_AS),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
class AverageSensor(SensorEntity):
|
||||
"""Implementation of an Average sensor."""
|
||||
|
||||
_unrecorded_attributes = frozenset(
|
||||
{
|
||||
ATTR_START,
|
||||
ATTR_END,
|
||||
ATTR_COUNT_SOURCES,
|
||||
ATTR_AVAILABLE_SOURCES,
|
||||
ATTR_COUNT,
|
||||
ATTR_MAX_VALUE,
|
||||
ATTR_MIN_VALUE,
|
||||
}
|
||||
)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
unique_id: Optional[str],
|
||||
name: str,
|
||||
start,
|
||||
end,
|
||||
duration,
|
||||
entity_ids: list,
|
||||
precision: int,
|
||||
undef,
|
||||
):
|
||||
"""Initialize the sensor."""
|
||||
self._start_template = start
|
||||
self._end_template = end
|
||||
self._duration = duration
|
||||
self._period = self.start = self.end = None
|
||||
self._precision = precision
|
||||
self._undef = undef
|
||||
self._temperature_mode = None
|
||||
|
||||
self.sources = expand_entity_ids(hass, entity_ids)
|
||||
self.count_sources = len(self.sources)
|
||||
self.available_sources = 0
|
||||
self.count = 0
|
||||
self.min_value = self.max_value = None
|
||||
|
||||
self._attr_name = name
|
||||
self._attr_native_value = None
|
||||
self._attr_native_unit_of_measurement = None
|
||||
self._attr_icon = None
|
||||
self._attr_state_class = SensorStateClass.MEASUREMENT
|
||||
self._attr_device_class = None
|
||||
#
|
||||
self._attr_unique_id = (
|
||||
str(
|
||||
sha1(
|
||||
";".join(
|
||||
[str(start), str(duration), str(end), ",".join(self.sources)]
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
)
|
||||
if unique_id == "__legacy__"
|
||||
else unique_id
|
||||
)
|
||||
|
||||
@property
|
||||
def _has_period(self) -> bool:
|
||||
"""Return True if sensor has any period setting."""
|
||||
return (
|
||||
self._start_template is not None
|
||||
or self._end_template is not None
|
||||
or self._duration is not None
|
||||
)
|
||||
|
||||
@property
|
||||
def should_poll(self) -> bool:
|
||||
"""Return the polling state."""
|
||||
return self._has_period
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self.available_sources > 0 and self._has_state(self._attr_native_value)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> Optional[Mapping[str, Any]]:
|
||||
"""Return entity specific state attributes."""
|
||||
state_attr = {
|
||||
attr: getattr(self, attr)
|
||||
for attr in ATTR_TO_PROPERTY
|
||||
if getattr(self, attr) is not None
|
||||
}
|
||||
return state_attr
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register callbacks."""
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@callback
|
||||
async def async_sensor_state_listener(entity, old_state, new_state):
|
||||
"""Handle device state changes."""
|
||||
last_state = self._attr_native_value
|
||||
await self._async_update_state()
|
||||
if last_state != self._attr_native_value:
|
||||
self.async_schedule_update_ha_state(True)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@callback
|
||||
async def async_sensor_startup(event):
|
||||
"""Update template on startup."""
|
||||
if self._has_period:
|
||||
self.async_schedule_update_ha_state(True)
|
||||
else:
|
||||
async_track_state_change(
|
||||
self.hass, self.sources, async_sensor_state_listener
|
||||
)
|
||||
await async_sensor_state_listener(None, None, None)
|
||||
|
||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_sensor_startup)
|
||||
|
||||
@staticmethod
|
||||
def _has_state(state) -> bool:
|
||||
"""Return True if state has any value."""
|
||||
return state is not None and state not in [
|
||||
STATE_UNKNOWN,
|
||||
STATE_UNAVAILABLE,
|
||||
"None",
|
||||
"",
|
||||
]
|
||||
|
||||
def _get_temperature(self, state: State) -> Optional[float]:
|
||||
"""Get temperature value from entity."""
|
||||
ha_unit = self.hass.config.units.temperature_unit
|
||||
domain = split_entity_id(state.entity_id)[0]
|
||||
if domain == WEATHER_DOMAIN:
|
||||
temperature = state.attributes.get("temperature")
|
||||
entity_unit = ha_unit
|
||||
elif domain in (CLIMATE_DOMAIN, WATER_HEATER_DOMAIN):
|
||||
temperature = state.attributes.get("current_temperature")
|
||||
entity_unit = ha_unit
|
||||
else:
|
||||
temperature = state.state
|
||||
entity_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
|
||||
if not self._has_state(temperature):
|
||||
return None
|
||||
|
||||
try:
|
||||
temperature = TemperatureConverter.convert(
|
||||
float(temperature), entity_unit, ha_unit
|
||||
)
|
||||
except ValueError as exc:
|
||||
_LOGGER.error('Could not convert value "%s" to float: %s', state, exc)
|
||||
return None
|
||||
|
||||
return temperature
|
||||
|
||||
def _get_state_value(self, state: State) -> Optional[float]:
|
||||
"""Return value of given entity state and count some sensor attributes."""
|
||||
state = self._get_temperature(state) if self._temperature_mode else state.state
|
||||
if not self._has_state(state):
|
||||
return self._undef
|
||||
|
||||
try:
|
||||
state = float(state)
|
||||
except ValueError as exc:
|
||||
_LOGGER.error('Could not convert value "%s" to float: %s', state, exc)
|
||||
return None
|
||||
|
||||
self.count += 1
|
||||
rstate = round(state, self._precision)
|
||||
if self.min_value is None:
|
||||
self.min_value = self.max_value = rstate
|
||||
else:
|
||||
self.min_value = min(self.min_value, rstate)
|
||||
self.max_value = max(self.max_value, rstate)
|
||||
return state
|
||||
|
||||
@Throttle(UPDATE_MIN_TIME)
|
||||
async def async_update(self):
|
||||
"""Update the sensor state if it needed."""
|
||||
if self._has_period:
|
||||
await self._async_update_state()
|
||||
|
||||
@staticmethod
|
||||
def handle_template_exception(exc, field):
|
||||
"""Log an error nicely if the template cannot be interpreted."""
|
||||
if exc.args and exc.args[0].startswith(
|
||||
"UndefinedError: 'None' has no attribute"
|
||||
):
|
||||
# Common during HA startup - so just a warning
|
||||
_LOGGER.warning(exc)
|
||||
|
||||
else:
|
||||
_LOGGER.error('Error parsing template for field "%s": %s', field, exc)
|
||||
|
||||
async def _async_update_period(self): # pylint: disable=too-many-branches
|
||||
"""Parse the templates and calculate a datetime tuples."""
|
||||
start = end = None
|
||||
now = dt_util.now()
|
||||
|
||||
# Parse start
|
||||
if self._start_template is not None:
|
||||
_LOGGER.debug("Process start template: %s", self._start_template)
|
||||
try:
|
||||
start_rendered = self._start_template.async_render()
|
||||
except (TemplateError, TypeError) as ex:
|
||||
self.handle_template_exception(ex, "start")
|
||||
return
|
||||
if isinstance(start_rendered, str):
|
||||
start = dt_util.parse_datetime(start_rendered)
|
||||
if start is None:
|
||||
try:
|
||||
start = dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(math.floor(float(start_rendered)))
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.error(
|
||||
'Parsing error: field "start" must be a datetime or a timestamp'
|
||||
)
|
||||
return
|
||||
|
||||
# Parse end
|
||||
if self._end_template is not None:
|
||||
_LOGGER.debug("Process end template: %s", self._end_template)
|
||||
try:
|
||||
end_rendered = self._end_template.async_render()
|
||||
except (TemplateError, TypeError) as ex:
|
||||
self.handle_template_exception(ex, "end")
|
||||
return
|
||||
if isinstance(end_rendered, str):
|
||||
end = dt_util.parse_datetime(end_rendered)
|
||||
if end is None:
|
||||
try:
|
||||
end = dt_util.as_local(
|
||||
dt_util.utc_from_timestamp(math.floor(float(end_rendered)))
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.error(
|
||||
'Parsing error: field "end" must be a datetime or a timestamp'
|
||||
)
|
||||
return
|
||||
|
||||
# Calculate start or end using the duration
|
||||
if self._duration is not None:
|
||||
_LOGGER.debug("Process duration: %s", self._duration)
|
||||
if start is None:
|
||||
if end is None:
|
||||
end = now
|
||||
start = end - self._duration
|
||||
else:
|
||||
end = start + self._duration
|
||||
|
||||
_LOGGER.debug("Calculation period: start=%s, end=%s", start, end)
|
||||
if start is None or end is None:
|
||||
return
|
||||
|
||||
if start > end:
|
||||
start, end = end, start
|
||||
|
||||
if start > now:
|
||||
# History hasn't been written yet for this period
|
||||
return
|
||||
end = min(end, now) # No point in making stats of the future
|
||||
|
||||
self._period = start, end
|
||||
self.start = start.replace(microsecond=0).isoformat()
|
||||
self.end = end.replace(microsecond=0).isoformat()
|
||||
|
||||
def _init_mode(self, state: State):
|
||||
"""Initialize sensor mode."""
|
||||
if self._temperature_mode is not None:
|
||||
return
|
||||
|
||||
domain = split_entity_id(state.entity_id)[0]
|
||||
self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
self._attr_native_unit_of_measurement = state.attributes.get(
|
||||
ATTR_UNIT_OF_MEASUREMENT
|
||||
)
|
||||
self._temperature_mode = (
|
||||
self._attr_device_class == SensorDeviceClass.TEMPERATURE
|
||||
or domain in (WEATHER_DOMAIN, CLIMATE_DOMAIN, WATER_HEATER_DOMAIN)
|
||||
or self._attr_native_unit_of_measurement in TEMPERATURE_UNITS
|
||||
)
|
||||
if self._temperature_mode:
|
||||
_LOGGER.debug("%s is a temperature entity.", state.entity_id)
|
||||
self._attr_device_class = SensorDeviceClass.TEMPERATURE
|
||||
self._attr_native_unit_of_measurement = (
|
||||
self.hass.config.units.temperature_unit
|
||||
)
|
||||
else:
|
||||
_LOGGER.debug("%s is NOT a temperature entity.", state.entity_id)
|
||||
self._attr_icon = state.attributes.get(ATTR_ICON)
|
||||
|
||||
async def _async_update_state(
|
||||
self,
|
||||
): # pylint: disable=too-many-locals,too-many-branches,too-many-statements
|
||||
"""Update the sensor state."""
|
||||
_LOGGER.debug('Updating sensor "%s"', self.name)
|
||||
start = end = start_ts = end_ts = None
|
||||
p_period = self._period
|
||||
|
||||
# Parse templates
|
||||
await self._async_update_period()
|
||||
|
||||
if self._period is not None:
|
||||
now = datetime.datetime.now()
|
||||
start, end = self._period
|
||||
if p_period is None:
|
||||
p_start = p_end = now
|
||||
else:
|
||||
p_start, p_end = p_period
|
||||
|
||||
# Convert times to UTC
|
||||
start = dt_util.as_utc(start)
|
||||
end = dt_util.as_utc(end)
|
||||
p_start = dt_util.as_utc(p_start)
|
||||
p_end = dt_util.as_utc(p_end)
|
||||
|
||||
# Compute integer timestamps
|
||||
now_ts = math.floor(dt_util.as_timestamp(now))
|
||||
start_ts = math.floor(dt_util.as_timestamp(start))
|
||||
end_ts = math.floor(dt_util.as_timestamp(end))
|
||||
p_start_ts = math.floor(dt_util.as_timestamp(p_start))
|
||||
p_end_ts = math.floor(dt_util.as_timestamp(p_end))
|
||||
|
||||
# If period has not changed and current time after the period end..
|
||||
if start_ts == p_start_ts and end_ts == p_end_ts and end_ts <= now_ts:
|
||||
# Don't compute anything as the value cannot have changed
|
||||
return
|
||||
|
||||
self.available_sources = 0
|
||||
values = []
|
||||
self.count = 0
|
||||
self.min_value = self.max_value = None
|
||||
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
for entity_id in self.sources:
|
||||
_LOGGER.debug('Processing entity "%s"', entity_id)
|
||||
|
||||
state = self.hass.states.get(entity_id) # type: State
|
||||
|
||||
if state is None:
|
||||
_LOGGER.error('Unable to find an entity "%s"', entity_id)
|
||||
continue
|
||||
|
||||
self._init_mode(state)
|
||||
|
||||
value = 0
|
||||
elapsed = 0
|
||||
|
||||
if self._period is None:
|
||||
# Get current state
|
||||
value = self._get_state_value(state)
|
||||
_LOGGER.debug("Current state: %s", value)
|
||||
|
||||
else:
|
||||
# Get history between start and now
|
||||
history_list = await get_instance(self.hass).async_add_executor_job(
|
||||
history.state_changes_during_period,
|
||||
self.hass,
|
||||
start,
|
||||
end,
|
||||
str(entity_id),
|
||||
)
|
||||
|
||||
if (
|
||||
entity_id not in history_list.keys()
|
||||
or history_list[entity_id] is None
|
||||
or len(history_list[entity_id]) == 0
|
||||
):
|
||||
value = self._get_state_value(state)
|
||||
_LOGGER.warning(
|
||||
'Historical data not found for entity "%s". '
|
||||
"Current state used: %s",
|
||||
entity_id,
|
||||
value,
|
||||
)
|
||||
else:
|
||||
# Get the first state
|
||||
item = history_list[entity_id][0]
|
||||
_LOGGER.debug("Initial historical state: %s", item)
|
||||
last_state = None
|
||||
last_time = start_ts
|
||||
if item is not None and self._has_state(item.state):
|
||||
last_state = self._get_state_value(item)
|
||||
|
||||
# Get the other states
|
||||
for item in history_list.get(entity_id):
|
||||
_LOGGER.debug("Historical state: %s", item)
|
||||
current_state = self._get_state_value(item)
|
||||
current_time = item.last_changed.timestamp()
|
||||
|
||||
if last_state is not None:
|
||||
last_elapsed = current_time - last_time
|
||||
value += last_state * last_elapsed
|
||||
elapsed += last_elapsed
|
||||
|
||||
last_state = current_state
|
||||
last_time = current_time
|
||||
|
||||
# Count time elapsed between last history state and now
|
||||
if last_state is None:
|
||||
value = None
|
||||
else:
|
||||
last_elapsed = end_ts - last_time
|
||||
value += last_state * last_elapsed
|
||||
elapsed += last_elapsed
|
||||
if elapsed:
|
||||
value /= elapsed
|
||||
|
||||
_LOGGER.debug("Historical average state: %s", value)
|
||||
|
||||
if isinstance(value, numbers.Number):
|
||||
values.append(value)
|
||||
self.available_sources += 1
|
||||
|
||||
if values:
|
||||
self._attr_native_value = round(sum(values) / len(values), self._precision)
|
||||
if self._precision < 1:
|
||||
self._attr_native_value = int(self._attr_native_value)
|
||||
else:
|
||||
self._attr_native_value = None
|
||||
|
||||
_LOGGER.debug(
|
||||
"Total average state: %s %s",
|
||||
self._attr_native_value,
|
||||
self._attr_native_unit_of_measurement,
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
reload:
|
||||
name: Reload
|
||||
description: Reload all average sensor entities
|
||||
@@ -0,0 +1,294 @@
|
||||
"""
|
||||
HACS gives you a powerful UI to handle downloads of all your custom needs.
|
||||
|
||||
For more details about this integration, please refer to the documentation at
|
||||
https://hacs.xyz/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from aiogithubapi import AIOGitHubAPIException, GitHub, GitHubAPI
|
||||
from aiogithubapi.const import ACCEPT_HEADERS
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.components.lovelace.system_health import system_health_info
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||
from homeassistant.const import Platform, __version__ as HAVERSION
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.loader import async_get_integration
|
||||
import voluptuous as vol
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN, MINIMUM_HA_VERSION, STARTUP
|
||||
from .data_client import HacsDataClient
|
||||
from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode
|
||||
from .frontend import async_register_frontend
|
||||
from .utils.configuration_schema import hacs_config_combined
|
||||
from .utils.data import HacsData
|
||||
from .utils.logger import LOGGER
|
||||
from .utils.queue_manager import QueueManager
|
||||
from .utils.version import version_left_higher_or_equal_then_right
|
||||
from .websocket import async_register_websocket_commands
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({DOMAIN: hacs_config_combined()}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_initialize_integration(
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
config_entry: ConfigEntry | None = None,
|
||||
config: dict[str, Any] | None = None,
|
||||
) -> bool:
|
||||
"""Initialize the integration"""
|
||||
hass.data[DOMAIN] = hacs = HacsBase()
|
||||
hacs.enable_hacs()
|
||||
|
||||
if config is not None:
|
||||
if DOMAIN not in config:
|
||||
return True
|
||||
if hacs.configuration.config_type == ConfigurationType.CONFIG_ENTRY:
|
||||
return True
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_type": ConfigurationType.YAML,
|
||||
**config[DOMAIN],
|
||||
"config": config[DOMAIN],
|
||||
}
|
||||
)
|
||||
|
||||
if config_entry is not None:
|
||||
if config_entry.source == SOURCE_IMPORT:
|
||||
hass.async_create_task(hass.config_entries.async_remove(config_entry.entry_id))
|
||||
return False
|
||||
|
||||
hacs.configuration.update_from_dict(
|
||||
{
|
||||
"config_entry": config_entry,
|
||||
"config_type": ConfigurationType.CONFIG_ENTRY,
|
||||
**config_entry.data,
|
||||
**config_entry.options,
|
||||
}
|
||||
)
|
||||
|
||||
integration = await async_get_integration(hass, DOMAIN)
|
||||
|
||||
hacs.set_stage(None)
|
||||
|
||||
hacs.log.info(STARTUP, integration.version)
|
||||
|
||||
clientsession = async_get_clientsession(hass)
|
||||
|
||||
hacs.integration = integration
|
||||
hacs.version = integration.version
|
||||
hacs.configuration.dev = integration.version == "0.0.0"
|
||||
hacs.hass = hass
|
||||
hacs.queue = QueueManager(hass=hass)
|
||||
hacs.data = HacsData(hacs=hacs)
|
||||
hacs.data_client = HacsDataClient(
|
||||
session=clientsession,
|
||||
client_name=f"HACS/{integration.version}",
|
||||
)
|
||||
hacs.system.running = True
|
||||
hacs.session = clientsession
|
||||
|
||||
hacs.core.lovelace_mode = LovelaceMode.YAML
|
||||
try:
|
||||
lovelace_info = await system_health_info(hacs.hass)
|
||||
hacs.core.lovelace_mode = LovelaceMode(lovelace_info.get("mode", "yaml"))
|
||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
# If this happens, the users YAML is not valid, we assume YAML mode
|
||||
pass
|
||||
hacs.log.debug("Configuration type: %s", hacs.configuration.config_type)
|
||||
hacs.core.config_path = hacs.hass.config.path()
|
||||
|
||||
if hacs.core.ha_version is None:
|
||||
hacs.core.ha_version = AwesomeVersion(HAVERSION)
|
||||
|
||||
## Legacy GitHub client
|
||||
hacs.github = GitHub(
|
||||
hacs.configuration.token,
|
||||
clientsession,
|
||||
headers={
|
||||
"User-Agent": f"HACS/{hacs.version}",
|
||||
"Accept": ACCEPT_HEADERS["preview"],
|
||||
},
|
||||
)
|
||||
|
||||
## New GitHub client
|
||||
hacs.githubapi = GitHubAPI(
|
||||
token=hacs.configuration.token,
|
||||
session=clientsession,
|
||||
**{"client_name": f"HACS/{hacs.version}"},
|
||||
)
|
||||
|
||||
async def async_startup():
|
||||
"""HACS startup tasks."""
|
||||
hacs.enable_hacs()
|
||||
|
||||
for location in (
|
||||
hass.config.path("custom_components/custom_updater.py"),
|
||||
hass.config.path("custom_components/custom_updater/__init__.py"),
|
||||
):
|
||||
if os.path.exists(location):
|
||||
hacs.log.critical(
|
||||
"This cannot be used with custom_updater. "
|
||||
"To use this you need to remove custom_updater form %s",
|
||||
location,
|
||||
)
|
||||
|
||||
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
return False
|
||||
|
||||
if not version_left_higher_or_equal_then_right(
|
||||
hacs.core.ha_version.string,
|
||||
MINIMUM_HA_VERSION,
|
||||
):
|
||||
hacs.log.critical(
|
||||
"You need HA version %s or newer to use this integration.",
|
||||
MINIMUM_HA_VERSION,
|
||||
)
|
||||
hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
return False
|
||||
|
||||
if not await hacs.data.restore():
|
||||
hacs.disable_hacs(HacsDisabledReason.RESTORE)
|
||||
return False
|
||||
|
||||
if not hacs.configuration.experimental:
|
||||
can_update = await hacs.async_can_update()
|
||||
hacs.log.debug("Can update %s repositories", can_update)
|
||||
|
||||
hacs.set_active_categories()
|
||||
|
||||
async_register_websocket_commands(hass)
|
||||
async_register_frontend(hass, hacs)
|
||||
|
||||
if hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
hass.async_create_task(
|
||||
async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, hacs.configuration.config)
|
||||
)
|
||||
hacs.log.info("Update entities are only supported when using UI configuration")
|
||||
|
||||
else:
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
config_entry,
|
||||
[Platform.SENSOR, Platform.UPDATE]
|
||||
if hacs.configuration.experimental
|
||||
else [Platform.SENSOR],
|
||||
)
|
||||
|
||||
hacs.set_stage(HacsStage.SETUP)
|
||||
if hacs.system.disabled:
|
||||
return False
|
||||
|
||||
# Schedule startup tasks
|
||||
async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)
|
||||
|
||||
hacs.set_stage(HacsStage.WAITING)
|
||||
hacs.log.info("Setup complete, waiting for Home Assistant before startup tasks starts")
|
||||
|
||||
return not hacs.system.disabled
|
||||
|
||||
async def async_try_startup(_=None):
|
||||
"""Startup wrapper for yaml config."""
|
||||
try:
|
||||
startup_result = await async_startup()
|
||||
except AIOGitHubAPIException:
|
||||
startup_result = False
|
||||
if not startup_result:
|
||||
if (
|
||||
hacs.configuration.config_type == ConfigurationType.YAML
|
||||
or hacs.system.disabled_reason != HacsDisabledReason.INVALID_TOKEN
|
||||
):
|
||||
hacs.log.info("Could not setup HACS, trying again in 15 min")
|
||||
async_call_later(hass, 900, async_try_startup)
|
||||
return
|
||||
hacs.enable_hacs()
|
||||
|
||||
await async_try_startup()
|
||||
|
||||
# Mischief managed!
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
|
||||
"""Set up this integration using yaml."""
|
||||
if DOMAIN in config:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_yaml_configuration",
|
||||
is_fixable=False,
|
||||
issue_domain=DOMAIN,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_yaml_configuration",
|
||||
learn_more_url="https://hacs.xyz/docs/configuration/options",
|
||||
)
|
||||
LOGGER.warning(
|
||||
"YAML configuration of HACS is deprecated and will be "
|
||||
"removed in version 2.0.0, there will be no automatic "
|
||||
"import of this. "
|
||||
"Please remove it from your configuration, "
|
||||
"restart Home Assistant and use the UI to configure it instead."
|
||||
)
|
||||
return await async_initialize_integration(hass=hass, config=config)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up this integration using UI."""
|
||||
config_entry.async_on_unload(config_entry.add_update_listener(async_reload_entry))
|
||||
setup_result = await async_initialize_integration(hass=hass, config_entry=config_entry)
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
return setup_result and not hacs.system.disabled
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Handle removal of an entry."""
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
|
||||
if hacs.queue.has_pending_tasks:
|
||||
hacs.log.warning("Pending tasks, can not unload, try again later.")
|
||||
return False
|
||||
|
||||
# Clear out pending queue
|
||||
hacs.queue.clear()
|
||||
|
||||
for task in hacs.recuring_tasks:
|
||||
# Cancel all pending tasks
|
||||
task()
|
||||
|
||||
# Store data
|
||||
await hacs.data.async_write(force=True)
|
||||
|
||||
try:
|
||||
if hass.data.get("frontend_panels", {}).get("hacs"):
|
||||
hacs.log.info("Removing sidepanel")
|
||||
hass.components.frontend.async_remove_panel("hacs")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
platforms = ["sensor"]
|
||||
if hacs.configuration.experimental:
|
||||
platforms.append("update")
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(config_entry, platforms)
|
||||
|
||||
hacs.set_stage(None)
|
||||
hacs.disable_hacs(HacsDisabledReason.REMOVED)
|
||||
|
||||
hass.data.pop(DOMAIN, None)
|
||||
|
||||
return unload_ok
|
||||
|
||||
|
||||
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
"""Reload the HACS config entry."""
|
||||
if not await async_unload_entry(hass, config_entry):
|
||||
return
|
||||
await async_setup_entry(hass, config_entry)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,247 @@
|
||||
"""Adds config flow for HACS."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aiogithubapi import (
|
||||
GitHubDeviceAPI,
|
||||
GitHubException,
|
||||
GitHubLoginDeviceModel,
|
||||
GitHubLoginOauthModel,
|
||||
)
|
||||
from aiogithubapi.common.const import OAUTH_USER_LOGIN
|
||||
from awesomeversion import AwesomeVersion
|
||||
from homeassistant.config_entries import ConfigFlow, OptionsFlow
|
||||
from homeassistant.const import __version__ as HAVERSION
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import UnknownFlow
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.loader import async_get_integration
|
||||
import voluptuous as vol
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import CLIENT_ID, DOMAIN, LOCALE, MINIMUM_HA_VERSION
|
||||
from .enums import ConfigurationType
|
||||
from .utils.configuration_schema import (
|
||||
APPDAEMON,
|
||||
COUNTRY,
|
||||
DEBUG,
|
||||
EXPERIMENTAL,
|
||||
NETDAEMON,
|
||||
RELEASE_LIMIT,
|
||||
SIDEPANEL_ICON,
|
||||
SIDEPANEL_TITLE,
|
||||
)
|
||||
from .utils.logger import LOGGER
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
class HacsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Config flow for HACS."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
hass: HomeAssistant
|
||||
activation_task: asyncio.Task | None = None
|
||||
device: GitHubDeviceAPI | None = None
|
||||
|
||||
_registration: GitHubLoginDeviceModel | None = None
|
||||
_activation: GitHubLoginOauthModel | None = None
|
||||
_reauth: bool = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize."""
|
||||
self._errors = {}
|
||||
self._user_input = {}
|
||||
|
||||
async def async_step_user(self, user_input):
|
||||
"""Handle a flow initialized by the user."""
|
||||
self._errors = {}
|
||||
if self._async_current_entries():
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
if self.hass.data.get(DOMAIN):
|
||||
return self.async_abort(reason="single_instance_allowed")
|
||||
|
||||
if user_input:
|
||||
if [x for x in user_input if x.startswith("acc_") and not user_input[x]]:
|
||||
self._errors["base"] = "acc"
|
||||
return await self._show_config_form(user_input)
|
||||
|
||||
self._user_input = user_input
|
||||
|
||||
return await self.async_step_device(user_input)
|
||||
|
||||
## Initial form
|
||||
return await self._show_config_form(user_input)
|
||||
|
||||
@callback
|
||||
def async_remove(self):
|
||||
"""Cleanup."""
|
||||
if self.activation_task and not self.activation_task.done():
|
||||
self.activation_task.cancel()
|
||||
|
||||
async def async_step_device(self, _user_input):
|
||||
"""Handle device steps."""
|
||||
|
||||
async def _wait_for_activation() -> None:
|
||||
try:
|
||||
response = await self.device.activation(device_code=self._registration.device_code)
|
||||
self._activation = response.data
|
||||
finally:
|
||||
|
||||
async def _progress():
|
||||
with suppress(UnknownFlow):
|
||||
await self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
|
||||
|
||||
self.hass.async_create_task(_progress())
|
||||
|
||||
if not self.device:
|
||||
integration = await async_get_integration(self.hass, DOMAIN)
|
||||
self.device = GitHubDeviceAPI(
|
||||
client_id=CLIENT_ID,
|
||||
session=aiohttp_client.async_get_clientsession(self.hass),
|
||||
**{"client_name": f"HACS/{integration.version}"},
|
||||
)
|
||||
try:
|
||||
response = await self.device.register()
|
||||
self._registration = response.data
|
||||
except GitHubException as exception:
|
||||
LOGGER.exception(exception)
|
||||
return self.async_abort(reason="could_not_register")
|
||||
|
||||
if self.activation_task is None:
|
||||
self.activation_task = self.hass.async_create_task(_wait_for_activation())
|
||||
|
||||
if self.activation_task.done():
|
||||
if (exception := self.activation_task.exception()) is not None:
|
||||
LOGGER.exception(exception)
|
||||
return self.async_show_progress_done(next_step_id="could_not_register")
|
||||
return self.async_show_progress_done(next_step_id="device_done")
|
||||
|
||||
return self.async_show_progress(
|
||||
step_id="device",
|
||||
progress_action="wait_for_device",
|
||||
description_placeholders={
|
||||
"url": OAUTH_USER_LOGIN,
|
||||
"code": self._registration.user_code,
|
||||
},
|
||||
)
|
||||
|
||||
async def _show_config_form(self, user_input):
|
||||
"""Show the configuration form to edit location data."""
|
||||
|
||||
if not user_input:
|
||||
user_input = {}
|
||||
|
||||
if AwesomeVersion(HAVERSION) < MINIMUM_HA_VERSION:
|
||||
return self.async_abort(
|
||||
reason="min_ha_version",
|
||||
description_placeholders={"version": MINIMUM_HA_VERSION},
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Required("acc_logs", default=user_input.get("acc_logs", False)): bool,
|
||||
vol.Required("acc_addons", default=user_input.get("acc_addons", False)): bool,
|
||||
vol.Required(
|
||||
"acc_untested", default=user_input.get("acc_untested", False)
|
||||
): bool,
|
||||
vol.Required("acc_disable", default=user_input.get("acc_disable", False)): bool,
|
||||
vol.Optional(
|
||||
"experimental", default=user_input.get("experimental", False)
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
errors=self._errors,
|
||||
)
|
||||
|
||||
async def async_step_device_done(self, user_input: dict[str, bool] | None = None):
|
||||
"""Handle device steps"""
|
||||
if self._reauth:
|
||||
existing_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
|
||||
self.hass.config_entries.async_update_entry(
|
||||
existing_entry, data={**existing_entry.data, "token": self._activation.access_token}
|
||||
)
|
||||
await self.hass.config_entries.async_reload(existing_entry.entry_id)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
return self.async_create_entry(
|
||||
title="",
|
||||
data={
|
||||
"token": self._activation.access_token,
|
||||
},
|
||||
options={
|
||||
"experimental": self._user_input.get("experimental", False),
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_could_not_register(self, _user_input=None):
|
||||
"""Handle issues that need transition await from progress step."""
|
||||
return self.async_abort(reason="could_not_register")
|
||||
|
||||
async def async_step_reauth(self, _user_input=None):
|
||||
"""Perform reauth upon an API authentication error."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(self, user_input=None):
|
||||
"""Dialog that informs the user that reauth is required."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=vol.Schema({}),
|
||||
)
|
||||
self._reauth = True
|
||||
return await self.async_step_device(None)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
return HacsOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class HacsOptionsFlowHandler(OptionsFlow):
|
||||
"""HACS config flow options handler."""
|
||||
|
||||
def __init__(self, config_entry):
|
||||
"""Initialize HACS options flow."""
|
||||
self.config_entry = config_entry
|
||||
|
||||
async def async_step_init(self, _user_input=None):
|
||||
"""Manage the options."""
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_user(self, user_input=None):
|
||||
"""Handle a flow initialized by the user."""
|
||||
hacs: HacsBase = self.hass.data.get(DOMAIN)
|
||||
if user_input is not None:
|
||||
limit = int(user_input.get(RELEASE_LIMIT, 5))
|
||||
if limit <= 0 or limit > 100:
|
||||
return self.async_abort(reason="release_limit_value")
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
if hacs is None or hacs.configuration is None:
|
||||
return self.async_abort(reason="not_setup")
|
||||
|
||||
if hacs.queue.has_pending_tasks:
|
||||
return self.async_abort(reason="pending_tasks")
|
||||
|
||||
if hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
schema = {vol.Optional("not_in_use", default=""): str}
|
||||
else:
|
||||
schema = {
|
||||
vol.Optional(SIDEPANEL_TITLE, default=hacs.configuration.sidepanel_title): str,
|
||||
vol.Optional(SIDEPANEL_ICON, default=hacs.configuration.sidepanel_icon): str,
|
||||
vol.Optional(RELEASE_LIMIT, default=hacs.configuration.release_limit): int,
|
||||
vol.Optional(COUNTRY, default=hacs.configuration.country): vol.In(LOCALE),
|
||||
vol.Optional(APPDAEMON, default=hacs.configuration.appdaemon): bool,
|
||||
vol.Optional(NETDAEMON, default=hacs.configuration.netdaemon): bool,
|
||||
vol.Optional(DEBUG, default=hacs.configuration.debug): bool,
|
||||
vol.Optional(EXPERIMENTAL, default=hacs.configuration.experimental): bool,
|
||||
}
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))
|
||||
@@ -0,0 +1,293 @@
|
||||
"""Constants for HACS"""
|
||||
from typing import TypeVar
|
||||
|
||||
from aiogithubapi.common.const import ACCEPT_HEADERS
|
||||
|
||||
NAME_SHORT = "HACS"
|
||||
DOMAIN = "hacs"
|
||||
CLIENT_ID = "395a8e669c5de9f7c6e8"
|
||||
MINIMUM_HA_VERSION = "2023.6.0"
|
||||
|
||||
URL_BASE = "/hacsfiles"
|
||||
|
||||
TV = TypeVar("TV")
|
||||
|
||||
PACKAGE_NAME = "custom_components.hacs"
|
||||
|
||||
DEFAULT_CONCURRENT_TASKS = 15
|
||||
DEFAULT_CONCURRENT_BACKOFF_TIME = 1
|
||||
|
||||
HACS_REPOSITORY_ID = "172733314"
|
||||
|
||||
HACS_ACTION_GITHUB_API_HEADERS = {
|
||||
"User-Agent": "HACS/action",
|
||||
"Accept": ACCEPT_HEADERS["preview"],
|
||||
}
|
||||
|
||||
VERSION_STORAGE = "6"
|
||||
STORENAME = "hacs"
|
||||
|
||||
HACS_SYSTEM_ID = "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"
|
||||
|
||||
STARTUP = """
|
||||
-------------------------------------------------------------------
|
||||
HACS (Home Assistant Community Store)
|
||||
|
||||
Version: %s
|
||||
This is a custom integration
|
||||
If you have any issues with this you need to open an issue here:
|
||||
https://github.com/hacs/integration/issues
|
||||
-------------------------------------------------------------------
|
||||
"""
|
||||
|
||||
LOCALE = [
|
||||
"ALL",
|
||||
"AF",
|
||||
"AL",
|
||||
"DZ",
|
||||
"AS",
|
||||
"AD",
|
||||
"AO",
|
||||
"AI",
|
||||
"AQ",
|
||||
"AG",
|
||||
"AR",
|
||||
"AM",
|
||||
"AW",
|
||||
"AU",
|
||||
"AT",
|
||||
"AZ",
|
||||
"BS",
|
||||
"BH",
|
||||
"BD",
|
||||
"BB",
|
||||
"BY",
|
||||
"BE",
|
||||
"BZ",
|
||||
"BJ",
|
||||
"BM",
|
||||
"BT",
|
||||
"BO",
|
||||
"BQ",
|
||||
"BA",
|
||||
"BW",
|
||||
"BV",
|
||||
"BR",
|
||||
"IO",
|
||||
"BN",
|
||||
"BG",
|
||||
"BF",
|
||||
"BI",
|
||||
"KH",
|
||||
"CM",
|
||||
"CA",
|
||||
"CV",
|
||||
"KY",
|
||||
"CF",
|
||||
"TD",
|
||||
"CL",
|
||||
"CN",
|
||||
"CX",
|
||||
"CC",
|
||||
"CO",
|
||||
"KM",
|
||||
"CG",
|
||||
"CD",
|
||||
"CK",
|
||||
"CR",
|
||||
"HR",
|
||||
"CU",
|
||||
"CW",
|
||||
"CY",
|
||||
"CZ",
|
||||
"CI",
|
||||
"DK",
|
||||
"DJ",
|
||||
"DM",
|
||||
"DO",
|
||||
"EC",
|
||||
"EG",
|
||||
"SV",
|
||||
"GQ",
|
||||
"ER",
|
||||
"EE",
|
||||
"ET",
|
||||
"FK",
|
||||
"FO",
|
||||
"FJ",
|
||||
"FI",
|
||||
"FR",
|
||||
"GF",
|
||||
"PF",
|
||||
"TF",
|
||||
"GA",
|
||||
"GM",
|
||||
"GE",
|
||||
"DE",
|
||||
"GH",
|
||||
"GI",
|
||||
"GR",
|
||||
"GL",
|
||||
"GD",
|
||||
"GP",
|
||||
"GU",
|
||||
"GT",
|
||||
"GG",
|
||||
"GN",
|
||||
"GW",
|
||||
"GY",
|
||||
"HT",
|
||||
"HM",
|
||||
"VA",
|
||||
"HN",
|
||||
"HK",
|
||||
"HU",
|
||||
"IS",
|
||||
"IN",
|
||||
"ID",
|
||||
"IR",
|
||||
"IQ",
|
||||
"IE",
|
||||
"IM",
|
||||
"IL",
|
||||
"IT",
|
||||
"JM",
|
||||
"JP",
|
||||
"JE",
|
||||
"JO",
|
||||
"KZ",
|
||||
"KE",
|
||||
"KI",
|
||||
"KP",
|
||||
"KR",
|
||||
"KW",
|
||||
"KG",
|
||||
"LA",
|
||||
"LV",
|
||||
"LB",
|
||||
"LS",
|
||||
"LR",
|
||||
"LY",
|
||||
"LI",
|
||||
"LT",
|
||||
"LU",
|
||||
"MO",
|
||||
"MK",
|
||||
"MG",
|
||||
"MW",
|
||||
"MY",
|
||||
"MV",
|
||||
"ML",
|
||||
"MT",
|
||||
"MH",
|
||||
"MQ",
|
||||
"MR",
|
||||
"MU",
|
||||
"YT",
|
||||
"MX",
|
||||
"FM",
|
||||
"MD",
|
||||
"MC",
|
||||
"MN",
|
||||
"ME",
|
||||
"MS",
|
||||
"MA",
|
||||
"MZ",
|
||||
"MM",
|
||||
"NA",
|
||||
"NR",
|
||||
"NP",
|
||||
"NL",
|
||||
"NC",
|
||||
"NZ",
|
||||
"NI",
|
||||
"NE",
|
||||
"NG",
|
||||
"NU",
|
||||
"NF",
|
||||
"MP",
|
||||
"NO",
|
||||
"OM",
|
||||
"PK",
|
||||
"PW",
|
||||
"PS",
|
||||
"PA",
|
||||
"PG",
|
||||
"PY",
|
||||
"PE",
|
||||
"PH",
|
||||
"PN",
|
||||
"PL",
|
||||
"PT",
|
||||
"PR",
|
||||
"QA",
|
||||
"RO",
|
||||
"RU",
|
||||
"RW",
|
||||
"RE",
|
||||
"BL",
|
||||
"SH",
|
||||
"KN",
|
||||
"LC",
|
||||
"MF",
|
||||
"PM",
|
||||
"VC",
|
||||
"WS",
|
||||
"SM",
|
||||
"ST",
|
||||
"SA",
|
||||
"SN",
|
||||
"RS",
|
||||
"SC",
|
||||
"SL",
|
||||
"SG",
|
||||
"SX",
|
||||
"SK",
|
||||
"SI",
|
||||
"SB",
|
||||
"SO",
|
||||
"ZA",
|
||||
"GS",
|
||||
"SS",
|
||||
"ES",
|
||||
"LK",
|
||||
"SD",
|
||||
"SR",
|
||||
"SJ",
|
||||
"SZ",
|
||||
"SE",
|
||||
"CH",
|
||||
"SY",
|
||||
"TW",
|
||||
"TJ",
|
||||
"TZ",
|
||||
"TH",
|
||||
"TL",
|
||||
"TG",
|
||||
"TK",
|
||||
"TO",
|
||||
"TT",
|
||||
"TN",
|
||||
"TR",
|
||||
"TM",
|
||||
"TC",
|
||||
"TV",
|
||||
"UG",
|
||||
"UA",
|
||||
"AE",
|
||||
"GB",
|
||||
"US",
|
||||
"UM",
|
||||
"UY",
|
||||
"UZ",
|
||||
"VU",
|
||||
"VE",
|
||||
"VN",
|
||||
"VG",
|
||||
"VI",
|
||||
"WF",
|
||||
"EH",
|
||||
"YE",
|
||||
"ZM",
|
||||
"ZW",
|
||||
]
|
||||
@@ -0,0 +1,57 @@
|
||||
"""HACS Data client."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout
|
||||
|
||||
from .exceptions import HacsException, HacsNotModifiedException
|
||||
|
||||
|
||||
class HacsDataClient:
|
||||
"""HACS Data client."""
|
||||
|
||||
def __init__(self, session: ClientSession, client_name: str) -> None:
|
||||
"""Initialize."""
|
||||
self._client_name = client_name
|
||||
self._etags = {}
|
||||
self._session = session
|
||||
|
||||
async def _do_request(
|
||||
self,
|
||||
filename: str,
|
||||
section: str | None = None,
|
||||
) -> dict[str, dict[str, Any]] | list[str]:
|
||||
"""Do request."""
|
||||
endpoint = "/".join([v for v in [section, filename] if v is not None])
|
||||
try:
|
||||
response = await self._session.get(
|
||||
f"https://data-v2.hacs.xyz/{endpoint}",
|
||||
timeout=ClientTimeout(total=60),
|
||||
headers={
|
||||
"User-Agent": self._client_name,
|
||||
"If-None-Match": self._etags.get(endpoint, ""),
|
||||
},
|
||||
)
|
||||
if response.status == 304:
|
||||
raise HacsNotModifiedException() from None
|
||||
response.raise_for_status()
|
||||
except HacsNotModifiedException:
|
||||
raise
|
||||
except asyncio.TimeoutError:
|
||||
raise HacsException("Timeout of 60s reached") from None
|
||||
except Exception as exception:
|
||||
raise HacsException(f"Error fetching data from HACS: {exception}") from exception
|
||||
|
||||
self._etags[endpoint] = response.headers.get("etag")
|
||||
|
||||
return await response.json()
|
||||
|
||||
async def get_data(self, section: str | None) -> dict[str, dict[str, Any]]:
|
||||
"""Get data."""
|
||||
return await self._do_request(filename="data.json", section=section)
|
||||
|
||||
async def get_repositories(self, section: str) -> list[str]:
|
||||
"""Get repositories."""
|
||||
return await self._do_request(filename="repositories.json", section=section)
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Diagnostics support for HACS."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogithubapi import GitHubException
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .base import HacsBase
|
||||
from .const import DOMAIN
|
||||
from .utils.configuration_schema import TOKEN
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
hacs: HacsBase = hass.data[DOMAIN]
|
||||
|
||||
data = {
|
||||
"entry": entry.as_dict(),
|
||||
"hacs": {
|
||||
"stage": hacs.stage,
|
||||
"version": hacs.version,
|
||||
"disabled_reason": hacs.system.disabled_reason,
|
||||
"new": hacs.status.new,
|
||||
"startup": hacs.status.startup,
|
||||
"categories": hacs.common.categories,
|
||||
"renamed_repositories": hacs.common.renamed_repositories,
|
||||
"archived_repositories": hacs.common.archived_repositories,
|
||||
"ignored_repositories": hacs.common.ignored_repositories,
|
||||
"lovelace_mode": hacs.core.lovelace_mode,
|
||||
"configuration": {},
|
||||
},
|
||||
"custom_repositories": [
|
||||
repo.data.full_name
|
||||
for repo in hacs.repositories.list_all
|
||||
if not hacs.repositories.is_default(str(repo.data.id))
|
||||
],
|
||||
"repositories": [],
|
||||
}
|
||||
|
||||
for key in (
|
||||
"appdaemon",
|
||||
"country",
|
||||
"debug",
|
||||
"dev",
|
||||
"experimental",
|
||||
"netdaemon",
|
||||
"python_script",
|
||||
"release_limit",
|
||||
"theme",
|
||||
):
|
||||
data["hacs"]["configuration"][key] = getattr(hacs.configuration, key, None)
|
||||
|
||||
for repository in hacs.repositories.list_downloaded:
|
||||
data["repositories"].append(
|
||||
{
|
||||
"data": repository.data.to_json(),
|
||||
"integration_manifest": repository.integration_manifest,
|
||||
"repository_manifest": repository.repository_manifest.to_dict(),
|
||||
"ref": repository.ref,
|
||||
"paths": {
|
||||
"localpath": repository.localpath.replace(hacs.core.config_path, "/config"),
|
||||
"local": repository.content.path.local.replace(
|
||||
hacs.core.config_path, "/config"
|
||||
),
|
||||
"remote": repository.content.path.remote,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
rate_limit_response = await hacs.githubapi.rate_limit()
|
||||
data["rate_limit"] = rate_limit_response.data.as_dict
|
||||
except GitHubException as exception:
|
||||
data["rate_limit"] = str(exception)
|
||||
|
||||
return async_redact_data(data, (TOKEN,))
|
||||
@@ -0,0 +1,119 @@
|
||||
"""HACS Base entities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN, HACS_SYSTEM_ID, NAME_SHORT
|
||||
from .enums import HacsDispatchEvent, HacsGitHubRepo
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .base import HacsBase
|
||||
from .repositories.base import HacsRepository
|
||||
|
||||
|
||||
def system_info(hacs: HacsBase) -> dict:
|
||||
"""Return system info."""
|
||||
return {
|
||||
"identifiers": {(DOMAIN, HACS_SYSTEM_ID)},
|
||||
"name": NAME_SHORT,
|
||||
"manufacturer": "hacs.xyz",
|
||||
"model": "",
|
||||
"sw_version": str(hacs.version),
|
||||
"configuration_url": "homeassistant://hacs",
|
||||
"entry_type": DeviceEntryType.SERVICE,
|
||||
}
|
||||
|
||||
|
||||
class HacsBaseEntity(Entity):
|
||||
"""Base HACS entity."""
|
||||
|
||||
repository: HacsRepository | None = None
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, hacs: HacsBase) -> None:
|
||||
"""Initialize."""
|
||||
self.hacs = hacs
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Register for status events."""
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
HacsDispatchEvent.REPOSITORY,
|
||||
self._update_and_write_state,
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _update(self) -> None:
|
||||
"""Update the sensor."""
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Manual updates of the sensor."""
|
||||
self._update()
|
||||
|
||||
@callback
|
||||
def _update_and_write_state(self, _: Any) -> None:
|
||||
"""Update the entity and write state."""
|
||||
self._update()
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
class HacsSystemEntity(HacsBaseEntity):
|
||||
"""Base system entity."""
|
||||
|
||||
_attr_icon = "hacs:hacs"
|
||||
_attr_unique_id = HACS_SYSTEM_ID
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict[str, any]:
|
||||
"""Return device information about HACS."""
|
||||
return system_info(self.hacs)
|
||||
|
||||
|
||||
class HacsRepositoryEntity(HacsBaseEntity):
|
||||
"""Base repository entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hacs: HacsBase,
|
||||
repository: HacsRepository,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(hacs=hacs)
|
||||
self.repository = repository
|
||||
self._attr_unique_id = str(repository.data.id)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return self.hacs.repositories.is_downloaded(repository_id=str(self.repository.data.id))
|
||||
|
||||
@property
|
||||
def device_info(self) -> dict[str, any]:
|
||||
"""Return device information about HACS."""
|
||||
if self.repository.data.full_name == HacsGitHubRepo.INTEGRATION:
|
||||
return system_info(self.hacs)
|
||||
|
||||
return {
|
||||
"identifiers": {(DOMAIN, str(self.repository.data.id))},
|
||||
"name": self.repository.display_name,
|
||||
"model": self.repository.data.category,
|
||||
"manufacturer": ", ".join(
|
||||
author.replace("@", "") for author in self.repository.data.authors
|
||||
),
|
||||
"configuration_url": "homeassistant://hacs",
|
||||
"entry_type": DeviceEntryType.SERVICE,
|
||||
}
|
||||
|
||||
@callback
|
||||
def _update_and_write_state(self, data: dict) -> None:
|
||||
"""Update the entity and write state."""
|
||||
if data.get("repository_id") == self.repository.data.id:
|
||||
self._update()
|
||||
self.async_write_ha_state()
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Helper constants."""
|
||||
# pylint: disable=missing-class-docstring
|
||||
import sys
|
||||
|
||||
if sys.version_info.minor >= 11:
|
||||
# Needs Python 3.11
|
||||
from enum import StrEnum # # pylint: disable=no-name-in-module
|
||||
else:
|
||||
try:
|
||||
# https://github.com/home-assistant/core/blob/dev/homeassistant/backports/enum.py
|
||||
# Considered internal to Home Assistant, can be removed whenever.
|
||||
from homeassistant.backports.enum import StrEnum
|
||||
except ImportError:
|
||||
from enum import Enum
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
pass
|
||||
|
||||
|
||||
class HacsGitHubRepo(StrEnum):
|
||||
"""HacsGitHubRepo."""
|
||||
|
||||
DEFAULT = "hacs/default"
|
||||
INTEGRATION = "hacs/integration"
|
||||
|
||||
|
||||
class HacsCategory(StrEnum):
|
||||
APPDAEMON = "appdaemon"
|
||||
INTEGRATION = "integration"
|
||||
LOVELACE = "lovelace"
|
||||
PLUGIN = "plugin" # Kept for legacy purposes
|
||||
NETDAEMON = "netdaemon"
|
||||
PYTHON_SCRIPT = "python_script"
|
||||
TEMPLATE = "template"
|
||||
THEME = "theme"
|
||||
REMOVED = "removed"
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
|
||||
class HacsDispatchEvent(StrEnum):
|
||||
"""HacsDispatchEvent."""
|
||||
|
||||
CONFIG = "hacs_dispatch_config"
|
||||
ERROR = "hacs_dispatch_error"
|
||||
RELOAD = "hacs_dispatch_reload"
|
||||
REPOSITORY = "hacs_dispatch_repository"
|
||||
REPOSITORY_DOWNLOAD_PROGRESS = "hacs_dispatch_repository_download_progress"
|
||||
STAGE = "hacs_dispatch_stage"
|
||||
STARTUP = "hacs_dispatch_startup"
|
||||
STATUS = "hacs_dispatch_status"
|
||||
|
||||
|
||||
class RepositoryFile(StrEnum):
|
||||
"""Repository file names."""
|
||||
|
||||
HACS_JSON = "hacs.json"
|
||||
MAINIFEST_JSON = "manifest.json"
|
||||
|
||||
|
||||
class ConfigurationType(StrEnum):
|
||||
YAML = "yaml"
|
||||
CONFIG_ENTRY = "config_entry"
|
||||
|
||||
|
||||
class LovelaceMode(StrEnum):
|
||||
"""Lovelace Modes."""
|
||||
|
||||
STORAGE = "storage"
|
||||
AUTO = "auto"
|
||||
AUTO_GEN = "auto-gen"
|
||||
YAML = "yaml"
|
||||
|
||||
|
||||
class HacsStage(StrEnum):
|
||||
SETUP = "setup"
|
||||
STARTUP = "startup"
|
||||
WAITING = "waiting"
|
||||
RUNNING = "running"
|
||||
BACKGROUND = "background"
|
||||
|
||||
|
||||
class HacsDisabledReason(StrEnum):
|
||||
RATE_LIMIT = "rate_limit"
|
||||
REMOVED = "removed"
|
||||
INVALID_TOKEN = "invalid_token"
|
||||
CONSTRAINS = "constrains"
|
||||
LOAD_HACS = "load_hacs"
|
||||
RESTORE = "restore"
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Custom Exceptions for HACS."""
|
||||
|
||||
|
||||
class HacsException(Exception):
|
||||
"""Super basic."""
|
||||
|
||||
|
||||
class HacsRepositoryArchivedException(HacsException):
|
||||
"""For repositories that are archived."""
|
||||
|
||||
|
||||
class HacsNotModifiedException(HacsException):
|
||||
"""For responses that are not modified."""
|
||||
|
||||
|
||||
class HacsExpectedException(HacsException):
|
||||
"""For stuff that are expected."""
|
||||
|
||||
|
||||
class HacsRepositoryExistException(HacsException):
|
||||
"""For repositories that are already exist."""
|
||||
|
||||
|
||||
class HacsExecutionStillInProgress(HacsException):
|
||||
"""Exception to raise if execution is still in progress."""
|
||||
|
||||
|
||||
class AddonRepositoryException(HacsException):
|
||||
"""Exception to raise when user tries to add add-on repository."""
|
||||
|
||||
exception_message = (
|
||||
"The repository does not seem to be a integration, "
|
||||
"but an add-on repository. HACS does not manage add-ons."
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(self.exception_message)
|
||||
|
||||
|
||||
class HomeAssistantCoreRepositoryException(HacsException):
|
||||
"""Exception to raise when user tries to add the home-assistant/core repository."""
|
||||
|
||||
exception_message = (
|
||||
"You can not add homeassistant/core, to use core integrations "
|
||||
"check the Home Assistant documentation for how to add them."
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(self.exception_message)
|
||||
@@ -0,0 +1,85 @@
|
||||
""""Starting setup task: Frontend"."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from .const import DOMAIN, URL_BASE
|
||||
from .hacs_frontend import VERSION as FE_VERSION, locate_dir
|
||||
from .hacs_frontend_experimental import (
|
||||
VERSION as EXPERIMENTAL_FE_VERSION,
|
||||
locate_dir as experimental_locate_dir,
|
||||
)
|
||||
|
||||
try:
|
||||
from homeassistant.components.frontend import add_extra_js_url
|
||||
except ImportError:
|
||||
|
||||
def add_extra_js_url(hass: HomeAssistant, url: str, es5: bool = False) -> None:
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
hacs.log.error("Could not import add_extra_js_url from frontend.")
|
||||
if "frontend_extra_module_url" not in hass.data:
|
||||
hass.data["frontend_extra_module_url"] = set()
|
||||
hass.data["frontend_extra_module_url"].add(url)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .base import HacsBase
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
||||
"""Register the frontend."""
|
||||
|
||||
# Setup themes endpoint if needed
|
||||
hacs.async_setup_frontend_endpoint_themes()
|
||||
|
||||
# Register frontend
|
||||
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
|
||||
hacs.log.warning(
|
||||
"<HacsFrontend> Frontend development mode enabled. Do not run in production!"
|
||||
)
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
|
||||
)
|
||||
elif hacs.configuration.experimental:
|
||||
hacs.log.info("<HacsFrontend> Using experimental frontend")
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", experimental_locate_dir(), cache_headers=False
|
||||
)
|
||||
else:
|
||||
#
|
||||
hass.http.register_static_path(f"{URL_BASE}/frontend", locate_dir(), cache_headers=False)
|
||||
|
||||
# Custom iconset
|
||||
hass.http.register_static_path(
|
||||
f"{URL_BASE}/iconset.js", str(hacs.integration_dir / "iconset.js")
|
||||
)
|
||||
add_extra_js_url(hass, f"{URL_BASE}/iconset.js")
|
||||
|
||||
hacs.frontend_version = (
|
||||
FE_VERSION if not hacs.configuration.experimental else EXPERIMENTAL_FE_VERSION
|
||||
)
|
||||
|
||||
# Add to sidepanel if needed
|
||||
if DOMAIN not in hass.data.get("frontend_panels", {}):
|
||||
hass.components.frontend.async_register_built_in_panel(
|
||||
component_name="custom",
|
||||
sidebar_title=hacs.configuration.sidepanel_title,
|
||||
sidebar_icon=hacs.configuration.sidepanel_icon,
|
||||
frontend_url_path=DOMAIN,
|
||||
config={
|
||||
"_panel_custom": {
|
||||
"name": "hacs-frontend",
|
||||
"embed_iframe": True,
|
||||
"trust_external": False,
|
||||
"js_url": f"/hacsfiles/frontend/entrypoint.js?hacstag={hacs.frontend_version}",
|
||||
}
|
||||
},
|
||||
require_admin=True,
|
||||
)
|
||||
|
||||
# Setup plugin endpoint if needed
|
||||
hacs.async_setup_frontend_endpoint_plugin()
|
||||
@@ -0,0 +1,5 @@
|
||||
"""HACS Frontend"""
|
||||
from .version import VERSION
|
||||
|
||||
def locate_dir():
|
||||
return __path__[0]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
|
||||
import{a as t,r as i,n as a}from"./main-ad130be7.js";import{L as n,s}from"./c.82eccc94.js";let r=t([a("ha-list-item")],(function(t,a){return{F:class extends a{constructor(...i){super(...i),t(this)}},d:[{kind:"get",static:!0,key:"styles",value:function(){return[s,i`
|
||||
:host {
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
}
|
||||
:host([graphic="avatar"]:not([twoLine])),
|
||||
:host([graphic="icon"]:not([twoLine])) {
|
||||
height: 48px;
|
||||
}
|
||||
span.material-icons:first-of-type {
|
||||
margin-inline-start: 0px !important;
|
||||
margin-inline-end: var(
|
||||
--mdc-list-item-graphic-margin,
|
||||
16px
|
||||
) !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
span.material-icons:last-of-type {
|
||||
margin-inline-start: auto !important;
|
||||
margin-inline-end: 0px !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
`]}}]}}),n);const e=t=>`https://brands.home-assistant.io/${t.useFallback?"_/":""}${t.domain}/${t.darkOptimized?"dark_":""}${t.type}.png`,o=t=>t.split("/")[4],p=t=>t.startsWith("https://brands.home-assistant.io/");export{r as H,e as b,o as e,p as i};
|
||||
@@ -0,0 +1,24 @@
|
||||
import{a as e,h as t,Y as i,e as n,i as o,$ as r,L as l,N as a,r as d,n as s}from"./main-ad130be7.js";import"./c.9b92f489.js";e([s("ha-button-menu")],(function(e,t){class s extends t{constructor(...t){super(...t),e(this)}}return{F:s,d:[{kind:"field",key:i,value:void 0},{kind:"field",decorators:[n()],key:"corner",value:()=>"TOP_START"},{kind:"field",decorators:[n()],key:"menuCorner",value:()=>"START"},{kind:"field",decorators:[n({type:Number})],key:"x",value:()=>null},{kind:"field",decorators:[n({type:Number})],key:"y",value:()=>null},{kind:"field",decorators:[n({type:Boolean})],key:"multi",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"activatable",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"disabled",value:()=>!1},{kind:"field",decorators:[n({type:Boolean})],key:"fixed",value:()=>!1},{kind:"field",decorators:[o("mwc-menu",!0)],key:"_menu",value:void 0},{kind:"get",key:"items",value:function(){var e;return null===(e=this._menu)||void 0===e?void 0:e.items}},{kind:"get",key:"selected",value:function(){var e;return null===(e=this._menu)||void 0===e?void 0:e.selected}},{kind:"method",key:"focus",value:function(){var e,t;null!==(e=this._menu)&&void 0!==e&&e.open?this._menu.focusItemAtIndex(0):null===(t=this._triggerButton)||void 0===t||t.focus()}},{kind:"method",key:"render",value:function(){return r`
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
|
||||
</div>
|
||||
<mwc-menu
|
||||
.corner=${this.corner}
|
||||
.menuCorner=${this.menuCorner}
|
||||
.fixed=${this.fixed}
|
||||
.multi=${this.multi}
|
||||
.activatable=${this.activatable}
|
||||
.y=${this.y}
|
||||
.x=${this.x}
|
||||
>
|
||||
<slot></slot>
|
||||
</mwc-menu>
|
||||
`}},{kind:"method",key:"firstUpdated",value:function(e){l(a(s.prototype),"firstUpdated",this).call(this,e),"rtl"===document.dir&&this.updateComplete.then((()=>{this.querySelectorAll("mwc-list-item").forEach((e=>{const t=document.createElement("style");t.innerHTML="span.material-icons:first-of-type { margin-left: var(--mdc-list-item-graphic-margin, 32px) !important; margin-right: 0px !important;}",e.shadowRoot.appendChild(t)}))}))}},{kind:"method",key:"_handleClick",value:function(){this.disabled||(this._menu.anchor=this,this._menu.show())}},{kind:"get",key:"_triggerButton",value:function(){return this.querySelector('ha-icon-button[slot="trigger"], mwc-button[slot="trigger"]')}},{kind:"method",key:"_setTriggerAria",value:function(){this._triggerButton&&(this._triggerButton.ariaHasPopup="menu")}},{kind:"get",static:!0,key:"styles",value:function(){return d`
|
||||
:host {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
::slotted([disabled]) {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
`}}]}}),t);
|
||||
@@ -0,0 +1,390 @@
|
||||
import{a as e,h as t,e as i,g as a,t as s,$ as o,j as r,R as n,w as l,r as h,n as c,m as d,L as p,N as u,o as v,b as f,aI as b,ai as m,c as k,E as g,aJ as y,aC as w,aK as x,aL as $,d as _,s as R}from"./main-ad130be7.js";import{f as z}from"./c.3243a8b0.js";import{c as j}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.2d5ed670.js";import"./c.97b7c4b0.js";import{r as F}from"./c.4204ca09.js";import{i as P}from"./c.21c042d4.js";import{s as I}from"./c.2645c235.js";import"./c.a5f69ed4.js";import"./c.3f859915.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";import"./c.f6611997.js";import"./c.743a15a1.js";import"./c.4266acdb.js";e([c("ha-tab")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"active",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[i()],key:"name",value:void 0},{kind:"field",decorators:[a("mwc-ripple")],key:"_ripple",value:void 0},{kind:"field",decorators:[s()],key:"_shouldRenderRipple",value:()=>!1},{kind:"method",key:"render",value:function(){return o`
|
||||
<div
|
||||
tabindex="0"
|
||||
role="tab"
|
||||
aria-selected=${this.active}
|
||||
aria-label=${r(this.name)}
|
||||
@focus=${this.handleRippleFocus}
|
||||
@blur=${this.handleRippleBlur}
|
||||
@mousedown=${this.handleRippleActivate}
|
||||
@mouseup=${this.handleRippleDeactivate}
|
||||
@mouseenter=${this.handleRippleMouseEnter}
|
||||
@mouseleave=${this.handleRippleMouseLeave}
|
||||
@touchstart=${this.handleRippleActivate}
|
||||
@touchend=${this.handleRippleDeactivate}
|
||||
@touchcancel=${this.handleRippleDeactivate}
|
||||
@keydown=${this._handleKeyDown}
|
||||
>
|
||||
${this.narrow?o`<slot name="icon"></slot>`:""}
|
||||
<span class="name">${this.name}</span>
|
||||
${this._shouldRenderRipple?o`<mwc-ripple></mwc-ripple>`:""}
|
||||
</div>
|
||||
`}},{kind:"field",key:"_rippleHandlers",value(){return new n((()=>(this._shouldRenderRipple=!0,this._ripple)))}},{kind:"method",key:"_handleKeyDown",value:function(e){13===e.keyCode&&e.target.click()}},{kind:"method",decorators:[l({passive:!0})],key:"handleRippleActivate",value:function(e){this._rippleHandlers.startPress(e)}},{kind:"method",key:"handleRippleDeactivate",value:function(){this._rippleHandlers.endPress()}},{kind:"method",key:"handleRippleMouseEnter",value:function(){this._rippleHandlers.startHover()}},{kind:"method",key:"handleRippleMouseLeave",value:function(){this._rippleHandlers.endHover()}},{kind:"method",key:"handleRippleFocus",value:function(){this._rippleHandlers.startFocus()}},{kind:"method",key:"handleRippleBlur",value:function(){this._rippleHandlers.endFocus()}},{kind:"get",static:!0,key:"styles",value:function(){return h`
|
||||
div {
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: var(--header-height);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:host([active]) {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
:host(:not([narrow])[active]) div {
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host([narrow]) div {
|
||||
padding: 0 4px;
|
||||
}
|
||||
`}}]}}),t),e([c("hass-tabs-subpage")],(function(e,t){class a extends t{constructor(...t){super(...t),e(this)}}return{F:a,d:[{kind:"field",decorators:[i({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[i({type:Boolean})],key:"supervisor",value:()=>!1},{kind:"field",decorators:[i({attribute:!1})],key:"localizeFunc",value:void 0},{kind:"field",decorators:[i({type:String,attribute:"back-path"})],key:"backPath",value:void 0},{kind:"field",decorators:[i()],key:"backCallback",value:void 0},{kind:"field",decorators:[i({type:Boolean,attribute:"main-page"})],key:"mainPage",value:()=>!1},{kind:"field",decorators:[i({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"tabs",value:void 0},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0,attribute:"is-wide"})],key:"isWide",value:()=>!1},{kind:"field",decorators:[i({type:Boolean,reflect:!0})],key:"rtl",value:()=>!1},{kind:"field",decorators:[s()],key:"_activeTab",value:void 0},{kind:"field",decorators:[F(".content")],key:"_savedScrollPos",value:void 0},{kind:"field",key:"_getTabs",value(){return d(((e,t,i,a,s,r,n)=>{const l=e.filter((e=>(!e.component||e.core||P(this.hass,e.component))&&(!e.advancedOnly||i)));if(l.length<2){if(1===l.length){const e=l[0];return[e.translationKey?n(e.translationKey):e.name]}return[""]}return l.map((e=>o`
|
||||
<a href=${e.path}>
|
||||
<ha-tab
|
||||
.hass=${this.hass}
|
||||
.active=${e.path===(null==t?void 0:t.path)}
|
||||
.narrow=${this.narrow}
|
||||
.name=${e.translationKey?n(e.translationKey):e.name}
|
||||
>
|
||||
${e.iconPath?o`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${e.iconPath}
|
||||
></ha-svg-icon>`:""}
|
||||
</ha-tab>
|
||||
</a>
|
||||
`))}))}},{kind:"method",key:"willUpdate",value:function(e){if(e.has("route")&&(this._activeTab=this.tabs.find((e=>`${this.route.prefix}${this.route.path}`.includes(e.path)))),e.has("hass")){const t=e.get("hass");t&&t.language===this.hass.language||(this.rtl=j(this.hass))}p(u(a.prototype),"willUpdate",this).call(this,e)}},{kind:"method",key:"render",value:function(){var e,t;const i=this._getTabs(this.tabs,this._activeTab,null===(e=this.hass.userData)||void 0===e?void 0:e.showAdvanced,this.hass.config.components,this.hass.language,this.narrow,this.localizeFunc||this.hass.localize),a=i.length>1;return o`
|
||||
<div class="toolbar">
|
||||
${this.mainPage||!this.backPath&&null!==(t=history.state)&&void 0!==t&&t.root?o`
|
||||
<ha-menu-button
|
||||
.hassio=${this.supervisor}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`:this.backPath?o`
|
||||
<a href=${this.backPath}>
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
</a>
|
||||
`:o`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
${this.narrow||!a?o`<div class="main-title">
|
||||
<slot name="header">${a?"":i[0]}</slot>
|
||||
</div>`:""}
|
||||
${a?o`
|
||||
<div id="tabbar" class=${v({"bottom-bar":this.narrow})}>
|
||||
${i}
|
||||
</div>
|
||||
`:""}
|
||||
<div id="toolbar-icon">
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content ${v({tabs:a})}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab" class=${v({tabs:a})}>
|
||||
<slot name="fab"></slot>
|
||||
</div>
|
||||
`}},{kind:"method",decorators:[l({passive:!0})],key:"_saveScrollPos",value:function(e){this._savedScrollPos=e.target.scrollTop}},{kind:"method",key:"_backTapped",value:function(){this.backCallback?this.backCallback():history.back()}},{kind:"get",static:!0,key:"styles",value:function(){return h`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
ha-menu-button {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
background-color: var(--sidebar-background-color);
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.bottom-bar a {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
#tabbar {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#tabbar > a {
|
||||
overflow: hidden;
|
||||
max-width: 45%;
|
||||
}
|
||||
|
||||
#tabbar.bottom-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--sidebar-background-color);
|
||||
border-top: 1px solid var(--divider-color);
|
||||
justify-content: space-around;
|
||||
z-index: 2;
|
||||
font-size: 12px;
|
||||
width: 100%;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
#tabbar:not(.bottom-bar) {
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) #toolbar-icon {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
.main-title {
|
||||
flex: 1;
|
||||
max-height: var(--header-height);
|
||||
line-height: 20px;
|
||||
color: var(--sidebar-text-color);
|
||||
margin: var(--main-title-margin, 0 0 0 24px);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
width: calc(
|
||||
100% - env(safe-area-inset-left) - env(safe-area-inset-right)
|
||||
);
|
||||
margin-left: env(safe-area-inset-left);
|
||||
margin-right: env(safe-area-inset-right);
|
||||
height: calc(100% - 1px - var(--header-height));
|
||||
height: calc(
|
||||
100% - 1px - var(--header-height) - env(safe-area-inset-bottom)
|
||||
);
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:host([narrow]) .content.tabs {
|
||||
height: calc(100% - 2 * var(--header-height));
|
||||
height: calc(
|
||||
100% - 2 * var(--header-height) - env(safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
#fab {
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
:host([narrow]) #fab.tabs {
|
||||
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||
}
|
||||
#fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
:host([rtl]) #fab {
|
||||
right: auto;
|
||||
left: calc(16px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([rtl][is-wide]) #fab {
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: auto;
|
||||
}
|
||||
`}}]}}),t);let E=e([c("hacs-store-panel")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({attribute:!1})],key:"filters",value:()=>({})},{kind:"field",decorators:[i({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[i()],key:"_searchInput",value:()=>""},{kind:"field",decorators:[i({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"narrow",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"isWide",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[i({attribute:!1})],key:"sections",value:void 0},{kind:"field",decorators:[i()],key:"section",value:void 0},{kind:"field",key:"_repositoriesInActiveSection",value(){return d(((e,t)=>[(null==e?void 0:e.filter((e=>{var i,a,s;return(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===t)))||void 0===a||null===(s=a.categories)||void 0===s?void 0:s.includes(e.category))&&e.installed})))||[],(null==e?void 0:e.filter((e=>{var i,a,s;return(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===t)))||void 0===a||null===(s=a.categories)||void 0===s?void 0:s.includes(e.category))&&e.new&&!e.installed})))||[]]))}},{kind:"get",key:"allRepositories",value:function(){const[e,t]=this._repositoriesInActiveSection(this.hacs.repositories,this.section);return t.concat(e)}},{kind:"field",key:"_filterRepositories",value:()=>d(z)},{kind:"get",key:"visibleRepositories",value:function(){const e=this.allRepositories.filter((e=>{var t,i;return null===(t=this.filters[this.section])||void 0===t||null===(i=t.find((t=>t.id===e.category)))||void 0===i?void 0:i.checked}));return this._filterRepositories(e,this._searchInput)}},{kind:"method",key:"firstUpdated",value:async function(){this.addEventListener("filter-change",(e=>this._updateFilters(e)))}},{kind:"method",key:"_updateFilters",value:function(e){var t;const i=null===(t=this.filters[this.section])||void 0===t?void 0:t.find((t=>t.id===e.detail.id));this.filters[this.section].find((e=>e.id===i.id)).checked=!i.checked,this.requestUpdate()}},{kind:"method",key:"render",value:function(){var e;if(!this.hacs)return o``;const t=this._repositoriesInActiveSection(this.hacs.repositories,this.section)[1];if(!this.filters[this.section]&&this.hacs.info.categories){var i;const e=null===(i=f(this.hacs.language,this.route))||void 0===i?void 0:i.categories;this.filters[this.section]=[],null==e||e.filter((e=>{var t;return null===(t=this.hacs.info)||void 0===t?void 0:t.categories.includes(e)})).forEach((e=>{this.filters[this.section].push({id:e,value:e,checked:!0})}))}return o`<hass-tabs-subpage
|
||||
back-path="/hacs/entry"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${this.hacs.sections}
|
||||
hasFab
|
||||
>
|
||||
<ha-icon-overflow-menu
|
||||
slot="toolbar-icon"
|
||||
narrow
|
||||
.hass=${this.hass}
|
||||
.items=${[{path:b,label:this.hacs.localize("menu.documentation"),action:()=>m.open("https://hacs.xyz/","_blank","noreferrer=true")},{path:k,label:"GitHub",action:()=>m.open("https://github.com/hacs","_blank","noreferrer=true")},{path:g,label:this.hacs.localize("menu.open_issue"),action:()=>m.open("https://hacs.xyz/docs/issues","_blank","noreferrer=true")},{path:y,label:this.hacs.localize("menu.custom_repositories"),disabled:this.hacs.info.disabled_reason,action:()=>this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"custom-repositories",repositories:this.hacs.repositories},bubbles:!0,composed:!0}))},{path:w,label:this.hacs.localize("menu.about"),action:()=>I(this,this.hacs)}]}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
${this.narrow?o`
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
class="header"
|
||||
slot="header"
|
||||
.label=${this.hacs.localize("search.downloaded")}
|
||||
.filter=${this._searchInput||""}
|
||||
@value-changed=${this._inputValueChanged}
|
||||
></search-input>
|
||||
`:o`<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
.label=${0===t.length?this.hacs.localize("search.downloaded"):this.hacs.localize("search.downloaded_new")}
|
||||
.filter=${this._searchInput||""}
|
||||
@value-changed=${this._inputValueChanged}
|
||||
></search-input>
|
||||
</div>`}
|
||||
<div class="content ${this.narrow?"narrow-content":""}">
|
||||
${(null===(e=this.filters[this.section])||void 0===e?void 0:e.length)>1?o`<div class="filters">
|
||||
<hacs-filter
|
||||
.hacs=${this.hacs}
|
||||
.filters="${this.filters[this.section]}"
|
||||
></hacs-filter>
|
||||
</div>`:""}
|
||||
${null!=t&&t.length?o`<ha-alert .rtl=${j(this.hass)}>
|
||||
${this.hacs.localize("store.new_repositories_note")}
|
||||
<mwc-button
|
||||
class="max-content"
|
||||
slot="action"
|
||||
.label=${this.hacs.localize("menu.dismiss")}
|
||||
@click=${this._clearAllNewRepositories}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-alert> `:""}
|
||||
<div class="container ${this.narrow?"narrow":""}">
|
||||
${void 0===this.hacs.repositories?"":0===this.allRepositories.length?this._renderEmpty():0===this.visibleRepositories.length?this._renderNoResultsFound():this._renderRepositories()}
|
||||
</div>
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
.label=${this.hacs.localize("store.explore")}
|
||||
.extended=${!this.narrow}
|
||||
@click=${this._addRepository}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${x}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</hass-tabs-subpage>`}},{kind:"method",key:"_renderRepositories",value:function(){return this.visibleRepositories.map((e=>o`<hacs-repository-card
|
||||
.hass=${this.hass}
|
||||
.hacs=${this.hacs}
|
||||
.repository=${e}
|
||||
.narrow=${this.narrow}
|
||||
?narrow=${this.narrow}
|
||||
></hacs-repository-card>`))}},{kind:"method",key:"_clearAllNewRepositories",value:async function(){var e;await $(this.hass,{categories:(null===(e=f(this.hacs.language,this.route))||void 0===e?void 0:e.categories)||[]})}},{kind:"method",key:"_renderNoResultsFound",value:function(){return o`<ha-alert
|
||||
.rtl=${j(this.hass)}
|
||||
alert-type="warning"
|
||||
.title="${this.hacs.localize("store.no_repositories")} 😕"
|
||||
>
|
||||
${this.hacs.localize("store.no_repositories_found_desc1",{searchInput:this._searchInput})}
|
||||
<br />
|
||||
${this.hacs.localize("store.no_repositories_found_desc2")}
|
||||
</ha-alert>`}},{kind:"method",key:"_renderEmpty",value:function(){return o`<ha-alert
|
||||
.title="${this.hacs.localize("store.no_repositories")} 😕"
|
||||
.rtl=${j(this.hass)}
|
||||
>
|
||||
${this.hacs.localize("store.no_repositories_desc1")}
|
||||
<br />
|
||||
${this.hacs.localize("store.no_repositories_desc2")}
|
||||
</ha-alert>`}},{kind:"method",key:"_inputValueChanged",value:function(e){this._searchInput=e.detail.value,window.localStorage.setItem("hacs-search",this._searchInput)}},{kind:"method",key:"_addRepository",value:function(){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"add-repository",repositories:this.hacs.repositories,section:this.section},bubbles:!0,composed:!0}))}},{kind:"get",static:!0,key:"styles",value:function(){return[_,R,h`
|
||||
.filter {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.content {
|
||||
height: calc(100vh - 128px);
|
||||
overflow: auto;
|
||||
}
|
||||
.narrow-content {
|
||||
height: calc(100vh - 128px);
|
||||
}
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
|
||||
justify-items: center;
|
||||
grid-gap: 8px 8px;
|
||||
padding: 8px 16px 16px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
hacs-repository-card {
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
hacs-repository-card[narrow] {
|
||||
width: 100%;
|
||||
}
|
||||
hacs-repository-card[narrow]:last-of-type {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
ha-alert {
|
||||
color: var(--hcv-text-color-primary);
|
||||
display: block;
|
||||
margin-top: -4px;
|
||||
}
|
||||
.narrow {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 0px;
|
||||
margin: 0;
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
}
|
||||
|
||||
search-input.header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed !important;
|
||||
}
|
||||
.max-content {
|
||||
width: max-content;
|
||||
}
|
||||
`]}}]}}),t);export{E as HacsStorePanel};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
|
||||
import{a as e,e as t,i,L as a,N as d,$ as r,r as n,n as o}from"./main-ad130be7.js";import{H as s}from"./c.0a1cf8d0.js";e([o("ha-clickable-list-item")],(function(e,o){class s extends o{constructor(...t){super(...t),e(this)}}return{F:s,d:[{kind:"field",decorators:[t()],key:"href",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"disableHref",value:()=>!1},{kind:"field",decorators:[t({type:Boolean,reflect:!0})],key:"openNewTab",value:()=>!1},{kind:"field",decorators:[i("a")],key:"_anchor",value:void 0},{kind:"method",key:"render",value:function(){const e=a(d(s.prototype),"render",this).call(this),t=this.href||"";return r`${this.disableHref?r`<a aria-role="option">${e}</a>`:r`<a
|
||||
aria-role="option"
|
||||
target=${this.openNewTab?"_blank":""}
|
||||
href=${t}
|
||||
>${e}</a
|
||||
>`}`}},{kind:"method",key:"firstUpdated",value:function(){a(d(s.prototype),"firstUpdated",this).call(this),this.addEventListener("keydown",(e=>{"Enter"!==e.key&&" "!==e.key||this._anchor.click()}))}},{kind:"get",static:!0,key:"styles",value:function(){return[a(d(s),"styles",this),n`
|
||||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`]}}]}}),s);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
const n=(n,o)=>n&&n.config.components.includes(o);export{n as i};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
import{al as e,am as a,aj as s,an as r,ao as u}from"./main-ad130be7.js";async function i(i,o,n){const t=new e("updateLovelaceResources"),l=await a(i),d=`/hacsfiles/${o.full_name.split("/")[1]}`,c=s({repository:o,version:n}),p=l.find((e=>e.url.includes(d)));t.debug({namespace:d,url:c,exsisting:p}),p&&p.url!==c?(t.debug(`Updating exsusting resource for ${d}`),await r(i,{url:c,resource_id:p.id,res_type:p.type})):l.map((e=>e.url)).includes(c)||(t.debug(`Adding ${c} to Lovelace resources`),await u(i,{url:c,res_type:"module"}))}export{i as u};
|
||||
@@ -0,0 +1 @@
|
||||
import{m as o}from"./c.f6611997.js";import{a as t}from"./c.4266acdb.js";const n=async(n,s)=>t(n,{title:"Home Assistant Community Store",confirmText:s.localize("common.close"),text:o.html(`\n **${s.localize("dialog_about.integration_version")}:** | ${s.info.version}\n --|--\n **${s.localize("dialog_about.frontend_version")}:** | 20220906112053\n **${s.localize("common.repositories")}:** | ${s.repositories.length}\n **${s.localize("dialog_about.downloaded_repositories")}:** | ${s.repositories.filter((o=>o.installed)).length}\n\n **${s.localize("dialog_about.useful_links")}:**\n\n - [General documentation](https://hacs.xyz/)\n - [Configuration](https://hacs.xyz/docs/configuration/start)\n - [FAQ](https://hacs.xyz/docs/faq/what)\n - [GitHub](https://github.com/hacs)\n - [Discord](https://discord.gg/apgchf8)\n - [Become a GitHub sponsor? ❤️](https://github.com/sponsors/ludeeus)\n - [BuyMe~~Coffee~~Beer? 🍺🙈](https://buymeacoffee.com/ludeeus)\n\n ***\n\n _Everything you find in HACS is **not** tested by Home Assistant, that includes HACS itself.\n The HACS and Home Assistant teams do not support **anything** you find here._`)});export{n as s};
|
||||
@@ -0,0 +1,61 @@
|
||||
import{a as r,h as a,e as o,r as e,$ as d,n as t}from"./main-ad130be7.js";r([t("ha-card")],(function(r,a){return{F:class extends a{constructor(...a){super(...a),r(this)}},d:[{kind:"field",decorators:[o()],key:"header",value:void 0},{kind:"field",decorators:[o({type:Boolean,reflect:!0})],key:"outlined",value:()=>!1},{kind:"get",static:!0,key:"styles",value:function(){return e`
|
||||
:host {
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
transition: all 0.3s ease-out;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([outlined]) {
|
||||
box-shadow: none;
|
||||
border-width: var(--ha-card-border-width, 1px);
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
}
|
||||
|
||||
.card-header,
|
||||
:host ::slotted(.card-header) {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-card-header-font-size, 24px);
|
||||
letter-spacing: -0.012em;
|
||||
line-height: 48px;
|
||||
padding: 12px 16px 16px;
|
||||
display: block;
|
||||
margin-block-start: 0px;
|
||||
margin-block-end: 0px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-content:not(:first-child)),
|
||||
slot:not(:first-child)::slotted(.card-content) {
|
||||
padding-top: 0px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-content) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-actions) {
|
||||
border-top: 1px solid var(--divider-color, #e8e8e8);
|
||||
padding: 5px 16px;
|
||||
}
|
||||
`}},{kind:"method",key:"render",value:function(){return d`
|
||||
${this.header?d`<h1 class="card-header">${this.header}</h1>`:d``}
|
||||
<slot></slot>
|
||||
`}}]}}),a);
|
||||
@@ -0,0 +1,121 @@
|
||||
import{a as e,h as t,e as n,t as i,i as o,$ as a,av as d,o as s,L as r,N as l,A as h,ae as c,r as p,n as u}from"./main-ad130be7.js";e([u("ha-expansion-panel")],(function(e,t){class u extends t{constructor(...t){super(...t),e(this)}}return{F:u,d:[{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"expanded",value:()=>!1},{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"outlined",value:()=>!1},{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"leftChevron",value:()=>!1},{kind:"field",decorators:[n()],key:"header",value:void 0},{kind:"field",decorators:[n()],key:"secondary",value:void 0},{kind:"field",decorators:[i()],key:"_showContent",value(){return this.expanded}},{kind:"field",decorators:[o(".container")],key:"_container",value:void 0},{kind:"method",key:"render",value:function(){return a`
|
||||
<div class="top">
|
||||
<div
|
||||
id="summary"
|
||||
@click=${this._toggleContainer}
|
||||
@keydown=${this._toggleContainer}
|
||||
@focus=${this._focusChanged}
|
||||
@blur=${this._focusChanged}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-expanded=${this.expanded}
|
||||
aria-controls="sect1"
|
||||
>
|
||||
${this.leftChevron?a`
|
||||
<ha-svg-icon
|
||||
.path=${d}
|
||||
class="summary-icon ${s({expanded:this.expanded})}"
|
||||
></ha-svg-icon>
|
||||
`:""}
|
||||
<slot name="header">
|
||||
<div class="header">
|
||||
${this.header}
|
||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||
</div>
|
||||
</slot>
|
||||
${this.leftChevron?"":a`
|
||||
<ha-svg-icon
|
||||
.path=${d}
|
||||
class="summary-icon ${s({expanded:this.expanded})}"
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
</div>
|
||||
<slot name="icons"></slot>
|
||||
</div>
|
||||
<div
|
||||
class="container ${s({expanded:this.expanded})}"
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
role="region"
|
||||
aria-labelledby="summary"
|
||||
aria-hidden=${!this.expanded}
|
||||
tabindex="-1"
|
||||
>
|
||||
${this._showContent?a`<slot></slot>`:""}
|
||||
</div>
|
||||
`}},{kind:"method",key:"willUpdate",value:function(e){r(l(u.prototype),"willUpdate",this).call(this,e),e.has("expanded")&&this.expanded&&(this._showContent=this.expanded,setTimeout((()=>{this.expanded&&(this._container.style.overflow="initial")}),300))}},{kind:"method",key:"_handleTransitionEnd",value:function(){this._container.style.removeProperty("height"),this._container.style.overflow=this.expanded?"initial":"hidden",this._showContent=this.expanded}},{kind:"method",key:"_toggleContainer",value:async function(e){if(e.defaultPrevented)return;if("keydown"===e.type&&"Enter"!==e.key&&" "!==e.key)return;e.preventDefault();const t=!this.expanded;h(this,"expanded-will-change",{expanded:t}),this._container.style.overflow="hidden",t&&(this._showContent=!0,await c());const n=this._container.scrollHeight;this._container.style.height=`${n}px`,t||setTimeout((()=>{this._container.style.height="0px"}),0),this.expanded=t,h(this,"expanded-changed",{expanded:this.expanded})}},{kind:"method",key:"_focusChanged",value:function(e){this.shadowRoot.querySelector(".top").classList.toggle("focused","focus"===e.type)}},{kind:"get",static:!0,key:"styles",value:function(){return p`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top.focused {
|
||||
background: var(--input-fill-color);
|
||||
}
|
||||
|
||||
:host([outlined]) {
|
||||
box-shadow: none;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
:host([leftchevron]) .summary-icon {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#summary {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: var(--expansion-panel-summary-padding, 0 8px);
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.summary-icon.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.header,
|
||||
::slotted([slot="header"]) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--expansion-panel-content-padding, 0 8px);
|
||||
overflow: hidden;
|
||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.container.expanded {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
display: block;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
`}}]}}),t);
|
||||
@@ -0,0 +1,50 @@
|
||||
import{a as e,h as i,e as t,i as a,$ as n,O as l,z as o,A as s,r as c,n as r,m as d}from"./main-ad130be7.js";import"./c.3f859915.js";e([r("search-input")],(function(e,i){return{F:class extends i{constructor(...i){super(...i),e(this)}},d:[{kind:"field",decorators:[t({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[t()],key:"filter",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"suffix",value:()=>!1},{kind:"field",decorators:[t({type:Boolean})],key:"autofocus",value:()=>!1},{kind:"field",decorators:[t({type:String})],key:"label",value:void 0},{kind:"method",key:"focus",value:function(){var e;null===(e=this._input)||void 0===e||e.focus()}},{kind:"field",decorators:[a("ha-textfield",!0)],key:"_input",value:void 0},{kind:"method",key:"render",value:function(){return n`
|
||||
<ha-textfield
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label||"Search"}
|
||||
.value=${this.filter||""}
|
||||
icon
|
||||
.iconTrailing=${this.filter||this.suffix}
|
||||
@input=${this._filterInputChanged}
|
||||
>
|
||||
<slot name="prefix" slot="leadingIcon">
|
||||
<ha-svg-icon
|
||||
tabindex="-1"
|
||||
class="prefix"
|
||||
.path=${l}
|
||||
></ha-svg-icon>
|
||||
</slot>
|
||||
<div class="trailing" slot="trailingIcon">
|
||||
${this.filter&&n`
|
||||
<ha-icon-button
|
||||
@click=${this._clearSearch}
|
||||
.label=${this.hass.localize("ui.common.clear")}
|
||||
.path=${o}
|
||||
class="clear-button"
|
||||
></ha-icon-button>
|
||||
`}
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
</ha-textfield>
|
||||
`}},{kind:"method",key:"_filterChanged",value:async function(e){s(this,"value-changed",{value:String(e)})}},{kind:"method",key:"_filterInputChanged",value:async function(e){this._filterChanged(e.target.value)}},{kind:"method",key:"_clearSearch",value:async function(){this._filterChanged("")}},{kind:"get",static:!0,key:"styles",value:function(){return c`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
}
|
||||
ha-svg-icon,
|
||||
ha-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-svg-icon {
|
||||
outline: none;
|
||||
}
|
||||
.clear-button {
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: inherit;
|
||||
}
|
||||
.trailing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`}}]}}),i);const u=d(((e,i)=>e.filter((e=>h(e.name).includes(h(i))||h(e.description).includes(h(i))||h(e.category).includes(h(i))||h(e.full_name).includes(h(i))||h(e.authors).includes(h(i))||h(e.domain).includes(h(i)))))),h=d((e=>String(e||"").toLocaleLowerCase().replace(/-|_| /g,"")));export{u as f};
|
||||
@@ -0,0 +1,94 @@
|
||||
import{a6 as e,a7 as t,a as o,h as i,e as n,$ as a,r,n as l}from"./main-ad130be7.js";e({_template:t`
|
||||
<style>
|
||||
:host {
|
||||
overflow: hidden; /* needed for text-overflow: ellipsis to work on ff */
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-center-justified;
|
||||
@apply --layout-flex;
|
||||
}
|
||||
|
||||
:host([two-line]) {
|
||||
min-height: var(--paper-item-body-two-line-min-height, 72px);
|
||||
}
|
||||
|
||||
:host([three-line]) {
|
||||
min-height: var(--paper-item-body-three-line-min-height, 88px);
|
||||
}
|
||||
|
||||
:host > ::slotted(*) {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:host > ::slotted([secondary]) {
|
||||
@apply --paper-font-body1;
|
||||
|
||||
color: var(--paper-item-body-secondary-color, var(--secondary-text-color));
|
||||
|
||||
@apply --paper-item-body-secondary;
|
||||
}
|
||||
</style>
|
||||
|
||||
<slot></slot>
|
||||
`,is:"paper-item-body"}),o([l("ha-settings-row")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[n({type:Boolean,reflect:!0})],key:"narrow",value:void 0},{kind:"field",decorators:[n({type:Boolean,attribute:"three-line"})],key:"threeLine",value:()=>!1},{kind:"method",key:"render",value:function(){return a`
|
||||
<div class="prefix-wrap">
|
||||
<slot name="prefix"></slot>
|
||||
<paper-item-body
|
||||
?two-line=${!this.threeLine}
|
||||
?three-line=${this.threeLine}
|
||||
>
|
||||
<slot name="heading"></slot>
|
||||
<div secondary><slot name="description"></slot></div>
|
||||
</paper-item-body>
|
||||
</div>
|
||||
<div class="content"><slot></slot></div>
|
||||
`}},{kind:"get",static:!0,key:"styles",value:function(){return r`
|
||||
:host {
|
||||
display: flex;
|
||||
padding: 0 16px;
|
||||
align-content: normal;
|
||||
align-self: auto;
|
||||
align-items: center;
|
||||
}
|
||||
paper-item-body {
|
||||
padding: 8px 16px 8px 0;
|
||||
}
|
||||
paper-item-body[two-line] {
|
||||
min-height: calc(
|
||||
var(--paper-item-body-two-line-min-height, 72px) - 16px
|
||||
);
|
||||
flex: 1;
|
||||
}
|
||||
.content {
|
||||
display: contents;
|
||||
}
|
||||
:host(:not([narrow])) .content {
|
||||
display: var(--settings-row-content-display, flex);
|
||||
justify-content: flex-end;
|
||||
flex: 1;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.content ::slotted(*) {
|
||||
width: var(--settings-row-content-width);
|
||||
}
|
||||
:host([narrow]) {
|
||||
align-items: normal;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
::slotted(ha-switch) {
|
||||
padding: 16px 0;
|
||||
}
|
||||
div[secondary] {
|
||||
white-space: normal;
|
||||
}
|
||||
.prefix-wrap {
|
||||
display: var(--settings-row-prefix-display);
|
||||
}
|
||||
:host([narrow]) .prefix-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`}}]}}),i);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,190 @@
|
||||
import{a as e,h as t,e as o,$ as r,aM as i,r as a,n as s,o as n,aL as d,d as c}from"./main-ad130be7.js";import"./c.2d5ed670.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";e([s("ha-icon-overflow-menu")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[o({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[o({type:Array})],key:"items",value:()=>[]},{kind:"field",decorators:[o({type:Boolean})],key:"narrow",value:()=>!1},{kind:"method",key:"render",value:function(){return r`
|
||||
${this.narrow?r` <!-- Collapsed representation for small screens -->
|
||||
<ha-button-menu
|
||||
@click=${this._handleIconOverflowMenuOpened}
|
||||
@closed=${this._handleIconOverflowMenuClosed}
|
||||
class="ha-icon-overflow-menu-overflow"
|
||||
corner="BOTTOM_START"
|
||||
absolute
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
.path=${i}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
|
||||
${this.items.map((e=>r`
|
||||
<mwc-list-item
|
||||
graphic="icon"
|
||||
.disabled=${e.disabled}
|
||||
@click=${e.action}
|
||||
>
|
||||
<div slot="graphic">
|
||||
<ha-svg-icon .path=${e.path}></ha-svg-icon>
|
||||
</div>
|
||||
${e.label}
|
||||
</mwc-list-item>
|
||||
`))}
|
||||
</ha-button-menu>`:r`
|
||||
<!-- Icon representation for big screens -->
|
||||
${this.items.map((e=>e.narrowOnly?"":r`<div>
|
||||
${e.tooltip?r`<paper-tooltip animation-delay="0" position="left">
|
||||
${e.tooltip}
|
||||
</paper-tooltip>`:""}
|
||||
<ha-icon-button
|
||||
@click=${e.action}
|
||||
.label=${e.label}
|
||||
.path=${e.path}
|
||||
.disabled=${e.disabled}
|
||||
></ha-icon-button>
|
||||
</div> `))}
|
||||
`}
|
||||
`}},{kind:"method",key:"_handleIconOverflowMenuOpened",value:function(){const e=this.closest(".mdc-data-table__row");e&&(e.style.zIndex="1")}},{kind:"method",key:"_handleIconOverflowMenuClosed",value:function(){const e=this.closest(".mdc-data-table__row");e&&(e.style.zIndex="")}},{kind:"get",static:!0,key:"styles",value:function(){return a`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`}}]}}),t);const l=e=>t=>({kind:"method",placement:"prototype",key:t.key,descriptor:{set(e){this[`__${String(t.key)}`]=e},get(){return this[`__${String(t.key)}`]},enumerable:!0,configurable:!0},finisher(o){const r=o.prototype.connectedCallback;o.prototype.connectedCallback=function(){if(r.call(this),this[t.key]){const o=this.renderRoot.querySelector(e);if(!o)return;o.scrollTop=this[t.key]}}}});e([s("hacs-repository-card")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[o({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[o({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[o({attribute:!1})],key:"repository",value:void 0},{kind:"field",decorators:[o({type:Boolean})],key:"narrow",value:void 0},{kind:"get",key:"_borderClass",value:function(){const e={};return this.hacs.addedToLovelace(this.hacs,this.repository)&&"pending-restart"!==this.repository.status?this.repository.pending_upgrade?e["status-update"]=!0:this.repository.new&&!this.repository.installed&&(e["status-new"]=!0):e["status-issue"]=!0,0!==Object.keys(e).length&&(e["status-border"]=!0),e}},{kind:"get",key:"_headerClass",value:function(){const e={};return this.hacs.addedToLovelace(this.hacs,this.repository)&&"pending-restart"!==this.repository.status?this.repository.pending_upgrade?e["update-header"]=!0:this.repository.new&&!this.repository.installed?e["new-header"]=!0:e["default-header"]=!0:e["issue-header"]=!0,e}},{kind:"get",key:"_headerTitle",value:function(){return this.hacs.addedToLovelace(this.hacs,this.repository)?"pending-restart"===this.repository.status?this.hacs.localize("repository_card.pending_restart"):this.repository.pending_upgrade?this.hacs.localize("repository_card.pending_update"):this.repository.new&&!this.repository.installed?this.hacs.localize("repository_card.new_repository"):"":this.hacs.localize("repository_card.not_loaded")}},{kind:"method",key:"render",value:function(){return r`
|
||||
<a href="/hacs/repository/${this.repository.id}">
|
||||
<ha-card class=${n(this._borderClass)} ?narrow=${this.narrow} outlined>
|
||||
<div class="card-content">
|
||||
<div class="group-header">
|
||||
<div class="status-header ${n(this._headerClass)}">${this._headerTitle}</div>
|
||||
|
||||
<div class="title pointer">
|
||||
<h1>${this.repository.name}</h1>
|
||||
${"integration"!==this.repository.category?r` <ha-chip>
|
||||
${this.hacs.localize(`common.${this.repository.category}`)}
|
||||
</ha-chip>`:""}
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">${this.repository.description}</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this.repository.new&&!this.repository.installed?r`<div>
|
||||
<mwc-button class="status-new" @click=${this._setNotNew}>
|
||||
${this.hacs.localize("repository_card.dismiss")}
|
||||
</mwc-button>
|
||||
</div>`:this.repository.pending_upgrade&&this.hacs.addedToLovelace(this.hacs,this.repository)?r`<div>
|
||||
<mwc-button class="update-header" @click=${this._updateRepository} raised>
|
||||
${this.hacs.localize("common.update")}
|
||||
</mwc-button>
|
||||
</div> `:""}
|
||||
</div>
|
||||
</ha-card>
|
||||
</a>
|
||||
`}},{kind:"method",key:"_updateRepository",value:function(e){e.preventDefault(),this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"update",repository:this.repository.id},bubbles:!0,composed:!0}))}},{kind:"method",key:"_setNotNew",value:async function(e){e.preventDefault(),await d(this.hass,{repository:String(this.repository.id)})}},{kind:"get",static:!0,key:"styles",value:function(){return[c,a`
|
||||
ha-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 195px;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card-content {
|
||||
padding: 0 0 3px 0;
|
||||
height: 100%;
|
||||
}
|
||||
.card-actions {
|
||||
border-top: none;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px;
|
||||
}
|
||||
.group-header {
|
||||
height: auto;
|
||||
align-content: center;
|
||||
}
|
||||
.group-header h1 {
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
font-size: 22px;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
min-height: 24px;
|
||||
}
|
||||
a {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.description {
|
||||
opacity: var(--dark-primary-opacity);
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
max-height: 52px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-new {
|
||||
border-color: var(--hcv-color-new);
|
||||
--mdc-theme-primary: var(--hcv-color-new);
|
||||
}
|
||||
|
||||
.status-update {
|
||||
border-color: var(--hcv-color-update);
|
||||
}
|
||||
|
||||
.status-issue {
|
||||
border-color: var(--hcv-color-error);
|
||||
}
|
||||
|
||||
.new-header {
|
||||
background-color: var(--hcv-color-new);
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.issue-header {
|
||||
background-color: var(--hcv-color-error);
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.update-header {
|
||||
background-color: var(--hcv-color-update);
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.default-header {
|
||||
padding: 2px 0 !important;
|
||||
}
|
||||
|
||||
mwc-button.update-header {
|
||||
--mdc-theme-primary: var(--hcv-color-update);
|
||||
--mdc-theme-on-primary: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
.status-border {
|
||||
border-style: solid;
|
||||
border-width: min(var(--ha-card-border-width, 1px), 10px);
|
||||
}
|
||||
|
||||
.status-header {
|
||||
top: 0;
|
||||
padding: 6px 1px;
|
||||
margin: -1px;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
border-top-left-radius: var(--ha-card-border-radius, 4px);
|
||||
border-top-right-radius: var(--ha-card-border-radius, 4px);
|
||||
}
|
||||
|
||||
ha-card[narrow] {
|
||||
width: calc(100% - 24px);
|
||||
margin: 11px;
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
padding: 4px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
`]}}]}}),t);export{l as r};
|
||||
@@ -0,0 +1 @@
|
||||
import{A as o}from"./main-ad130be7.js";const a=()=>import("./c.f12697b4.js"),i=(i,l,m)=>new Promise((n=>{const r=l.cancel,s=l.confirm;o(i,"show-dialog",{dialogTag:"dialog-box",dialogImport:a,dialogParams:{...l,...m,cancel:()=>{n(!(null==m||!m.prompt)&&null),r&&r()},confirm:o=>{n(null==m||!m.prompt||o),s&&s(o)}}})})),l=(o,a)=>i(o,a),m=(o,a)=>i(o,a,{confirmation:!0}),n=(o,a)=>i(o,a,{prompt:!0});export{l as a,n as b,m as s};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
function t(t){const a=t.language||"en";return t.translationMetadata.translations[a]&&t.translationMetadata.translations[a].isRTL||!1}function a(a){return t(a)?"rtl":"ltr"}export{a,t as c};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,59 @@
|
||||
import{a as o,H as t,e as s,t as e,m as i,a0 as a,a1 as r,$ as l,aj as n,ak as h,a3 as c,ai as d,d as p,r as _,n as m}from"./main-ad130be7.js";import{c as y}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.d262aab0.js";import{s as v}from"./c.4266acdb.js";import{f as g,r as u,a as f}from"./c.fe747ba2.js";import{u as w}from"./c.25ed1ae4.js";import"./c.5d3ce9d6.js";import"./c.82e03b89.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.3f859915.js";import"./c.0ca5587f.js";import"./c.42d6aebd.js";import"./c.710a50fc.js";let b=o([m("hacs-download-dialog")],(function(o,t){return{F:class extends t{constructor(...t){super(...t),o(this)}},d:[{kind:"field",decorators:[s()],key:"repository",value:void 0},{kind:"field",decorators:[e()],key:"_toggle",value:()=>!0},{kind:"field",decorators:[e()],key:"_installing",value:()=>!1},{kind:"field",decorators:[e()],key:"_error",value:void 0},{kind:"field",decorators:[e()],key:"_repository",value:void 0},{kind:"field",decorators:[e()],key:"_downloadRepositoryData",value:()=>({beta:!1,version:""})},{kind:"method",key:"shouldUpdate",value:function(o){return o.forEach(((o,t)=>{"hass"===t&&(this.sidebarDocked='"docked"'===window.localStorage.getItem("dockedSidebar")),"repositories"===t&&this._fetchRepository()})),o.has("sidebarDocked")||o.has("narrow")||o.has("active")||o.has("_toggle")||o.has("_error")||o.has("_repository")||o.has("_downloadRepositoryData")||o.has("_installing")}},{kind:"field",key:"_getInstallPath",value:()=>i((o=>{let t=o.local_path;return"theme"===o.category&&(t=`${t}/${o.file_name}`),t}))},{kind:"method",key:"firstUpdated",value:async function(){var o;await this._fetchRepository(),this._toggle=!1,a(this.hass,(o=>this._error=o),r.ERROR),this._downloadRepositoryData.beta=this._repository.beta,this._downloadRepositoryData.version="version"===(null===(o=this._repository)||void 0===o?void 0:o.version_or_commit)?this._repository.releases[0]:""}},{kind:"method",key:"_fetchRepository",value:async function(){this._repository=await g(this.hass,this.repository)}},{kind:"method",key:"render",value:function(){var o;if(!this.active||!this._repository)return l``;const t=this._getInstallPath(this._repository),s=[{name:"beta",selector:{boolean:{}}},{name:"version",selector:{select:{options:"version"===this._repository.version_or_commit?this._repository.releases.concat("hacs/integration"===this._repository.full_name||this._repository.hide_default_branch?[]:[this._repository.default_branch]):[],mode:"dropdown"}}}];return l`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.secondary=${this.secondary}
|
||||
.title=${this._repository.name}
|
||||
>
|
||||
<div class="content">
|
||||
${"version"===this._repository.version_or_commit?l`
|
||||
<ha-form
|
||||
.disabled=${this._toggle}
|
||||
?narrow=${this.narrow}
|
||||
.data=${this._downloadRepositoryData}
|
||||
.schema=${s}
|
||||
.computeLabel=${o=>"beta"===o.name?this.hacs.localize("dialog_download.show_beta"):this.hacs.localize("dialog_download.select_version")}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-form>
|
||||
`:""}
|
||||
${this._repository.can_download?"":l`<ha-alert alert-type="error" .rtl=${y(this.hass)}>
|
||||
${this.hacs.localize("confirm.home_assistant_version_not_correct",{haversion:this.hass.config.version,minversion:this._repository.homeassistant})}
|
||||
</ha-alert>`}
|
||||
<div class="note">
|
||||
${this.hacs.localize("dialog_download.note_downloaded",{location:l`<code>'${t}'</code>`})}
|
||||
${"plugin"===this._repository.category&&"storage"!==this.hacs.info.lovelace_mode?l`
|
||||
<p>${this.hacs.localize("dialog_download.lovelace_instruction")}</p>
|
||||
<pre>
|
||||
url: ${n({repository:this._repository,skipTag:!0})}
|
||||
type: module
|
||||
</pre
|
||||
>
|
||||
`:""}
|
||||
${"integration"===this._repository.category?l`<p>${this.hacs.localize("dialog_download.restart")}</p>`:""}
|
||||
</div>
|
||||
${null!==(o=this._error)&&void 0!==o&&o.message?l`<ha-alert alert-type="error" .rtl=${y(this.hass)}>
|
||||
${this._error.message}
|
||||
</ha-alert>`:""}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryaction"
|
||||
?disabled=${!(this._repository.can_download&&!this._toggle&&"version"!==this._repository.version_or_commit)&&!this._downloadRepositoryData.version}
|
||||
@click=${this._installRepository}
|
||||
>
|
||||
${this._installing?l`<ha-circular-progress active size="small"></ha-circular-progress>`:this.hacs.localize("common.download")}
|
||||
</mwc-button>
|
||||
</hacs-dialog>
|
||||
`}},{kind:"method",key:"_valueChanged",value:async function(o){let t=!1;if(this._downloadRepositoryData.beta!==o.detail.value.beta&&(t=!0,this._toggle=!0,await h(this.hass,this.repository,o.detail.value.beta)),o.detail.value.version&&(t=!0,this._toggle=!0,await u(this.hass,this.repository,o.detail.value.version)),t){const o=await c(this.hass);await this._fetchRepository(),this.dispatchEvent(new CustomEvent("update-hacs",{detail:{repositories:o},bubbles:!0,composed:!0})),this._toggle=!1}this._downloadRepositoryData=o.detail.value}},{kind:"method",key:"_installRepository",value:async function(){var o;if(this._installing=!0,!this._repository)return;const t=this._downloadRepositoryData.version||this._repository.available_version||this._repository.default_branch;"commit"!==(null===(o=this._repository)||void 0===o?void 0:o.version_or_commit)?await f(this.hass,String(this._repository.id),t):await f(this.hass,String(this._repository.id)),this.hacs.log.debug(this._repository.category,"_installRepository"),this.hacs.log.debug(this.hacs.info.lovelace_mode,"_installRepository"),"plugin"===this._repository.category&&"storage"===this.hacs.info.lovelace_mode&&await w(this.hass,this._repository,t),this._installing=!1,this.dispatchEvent(new Event("hacs-secondary-dialog-closed",{bubbles:!0,composed:!0})),this.dispatchEvent(new Event("hacs-dialog-closed",{bubbles:!0,composed:!0})),"plugin"===this._repository.category&&v(this,{title:this.hacs.localize("common.reload"),text:l`${this.hacs.localize("dialog.reload.description")}<br />${this.hacs.localize("dialog.reload.confirm")}`,dismissText:this.hacs.localize("common.cancel"),confirmText:this.hacs.localize("common.reload"),confirm:()=>{d.location.href=d.location.href}})}},{kind:"get",static:!0,key:"styles",value:function(){return[p,_`
|
||||
.note {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.lovelace {
|
||||
margin-top: 8px;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-line;
|
||||
user-select: all;
|
||||
}
|
||||
`]}}]}}),t);export{b as HacsDonwloadDialog};
|
||||
@@ -0,0 +1,176 @@
|
||||
import{a6 as t,a7 as i,a8 as a}from"./main-ad130be7.js";t({_template:i`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
position: absolute;
|
||||
outline: none;
|
||||
z-index: 1002;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#tooltip {
|
||||
display: block;
|
||||
outline: none;
|
||||
@apply --paper-font-common-base;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
background-color: var(--paper-tooltip-background, #616161);
|
||||
color: var(--paper-tooltip-text-color, white);
|
||||
padding: 8px;
|
||||
border-radius: 2px;
|
||||
@apply --paper-tooltip;
|
||||
}
|
||||
|
||||
@keyframes keyFrameScaleUp {
|
||||
0% {
|
||||
transform: scale(0.0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameScaleDown {
|
||||
0% {
|
||||
transform: scale(1.0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameFadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameFadeOutOpacity {
|
||||
0% {
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameSlideDownIn {
|
||||
0% {
|
||||
transform: translateY(-2000px);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyFrameSlideDownOut {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
}
|
||||
10% {
|
||||
opacity: 0.2;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-2000px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-animation {
|
||||
opacity: 0;
|
||||
animation-delay: var(--paper-tooltip-delay-in, 500ms);
|
||||
animation-name: keyFrameFadeInOpacity;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-in, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.fade-out-animation {
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-out, 0ms);
|
||||
animation-name: keyFrameFadeOutOpacity;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.scale-up-animation {
|
||||
transform: scale(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-in, 500ms);
|
||||
animation-name: keyFrameScaleUp;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-in, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.scale-down-animation {
|
||||
transform: scale(1);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-out, 500ms);
|
||||
animation-name: keyFrameScaleDown;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in;
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.slide-down-animation {
|
||||
transform: translateY(-2000px);
|
||||
opacity: 0;
|
||||
animation-delay: var(--paper-tooltip-delay-out, 500ms);
|
||||
animation-name: keyFrameSlideDownIn;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.slide-down-animation-out {
|
||||
transform: translateY(0);
|
||||
opacity: var(--paper-tooltip-opacity, 0.9);
|
||||
animation-delay: var(--paper-tooltip-delay-out, 500ms);
|
||||
animation-name: keyFrameSlideDownOut;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: cubic-bezier(0.4, 0.0, 1, 1);
|
||||
animation-duration: var(--paper-tooltip-duration-out, 500ms);
|
||||
animation-fill-mode: forwards;
|
||||
@apply --paper-tooltip-animation;
|
||||
}
|
||||
|
||||
.cancel-animation {
|
||||
animation-delay: -30s !important;
|
||||
}
|
||||
|
||||
/* Thanks IE 10. */
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="tooltip" class="hidden">
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,is:"paper-tooltip",hostAttributes:{role:"tooltip",tabindex:-1},properties:{for:{type:String,observer:"_findTarget"},manualMode:{type:Boolean,value:!1,observer:"_manualModeChanged"},position:{type:String,value:"bottom"},fitToVisibleBounds:{type:Boolean,value:!1},offset:{type:Number,value:14},marginTop:{type:Number,value:14},animationDelay:{type:Number,value:500,observer:"_delayChange"},animationEntry:{type:String,value:""},animationExit:{type:String,value:""},animationConfig:{type:Object,value:function(){return{entry:[{name:"fade-in-animation",node:this,timing:{delay:0}}],exit:[{name:"fade-out-animation",node:this}]}}},_showing:{type:Boolean,value:!1}},listeners:{webkitAnimationEnd:"_onAnimationEnd"},get target(){var t=a(this).parentNode,i=a(this).getOwnerRoot();return this.for?a(i).querySelector("#"+this.for):t.nodeType==Node.DOCUMENT_FRAGMENT_NODE?i.host:t},attached:function(){this._findTarget()},detached:function(){this.manualMode||this._removeListeners()},playAnimation:function(t){"entry"===t?this.show():"exit"===t&&this.hide()},cancelAnimation:function(){this.$.tooltip.classList.add("cancel-animation")},show:function(){if(!this._showing){if(""===a(this).textContent.trim()){for(var t=!0,i=a(this).getEffectiveChildNodes(),n=0;n<i.length;n++)if(""!==i[n].textContent.trim()){t=!1;break}if(t)return}this._showing=!0,this.$.tooltip.classList.remove("hidden"),this.$.tooltip.classList.remove("cancel-animation"),this.$.tooltip.classList.remove(this._getAnimationType("exit")),this.updatePosition(),this._animationPlaying=!0,this.$.tooltip.classList.add(this._getAnimationType("entry"))}},hide:function(){if(this._showing){if(this._animationPlaying)return this._showing=!1,void this._cancelAnimation();this._onAnimationFinish(),this._showing=!1,this._animationPlaying=!0}},updatePosition:function(){if(this._target&&this.offsetParent){var t=this.offset;14!=this.marginTop&&14==this.offset&&(t=this.marginTop);var i,a,n=this.offsetParent.getBoundingClientRect(),e=this._target.getBoundingClientRect(),o=this.getBoundingClientRect(),s=(e.width-o.width)/2,r=(e.height-o.height)/2,l=e.left-n.left,p=e.top-n.top;switch(this.position){case"top":i=l+s,a=p-o.height-t;break;case"bottom":i=l+s,a=p+e.height+t;break;case"left":i=l-o.width-t,a=p+r;break;case"right":i=l+e.width+t,a=p+r}this.fitToVisibleBounds?(n.left+i+o.width>window.innerWidth?(this.style.right="0px",this.style.left="auto"):(this.style.left=Math.max(0,i)+"px",this.style.right="auto"),n.top+a+o.height>window.innerHeight?(this.style.bottom=n.height-p+t+"px",this.style.top="auto"):(this.style.top=Math.max(-n.top,a)+"px",this.style.bottom="auto")):(this.style.left=i+"px",this.style.top=a+"px")}},_addListeners:function(){this._target&&(this.listen(this._target,"mouseenter","show"),this.listen(this._target,"focus","show"),this.listen(this._target,"mouseleave","hide"),this.listen(this._target,"blur","hide"),this.listen(this._target,"tap","hide")),this.listen(this.$.tooltip,"animationend","_onAnimationEnd"),this.listen(this,"mouseenter","hide")},_findTarget:function(){this.manualMode||this._removeListeners(),this._target=this.target,this.manualMode||this._addListeners()},_delayChange:function(t){500!==t&&this.updateStyles({"--paper-tooltip-delay-in":t+"ms"})},_manualModeChanged:function(){this.manualMode?this._removeListeners():this._addListeners()},_cancelAnimation:function(){this.$.tooltip.classList.remove(this._getAnimationType("entry")),this.$.tooltip.classList.remove(this._getAnimationType("exit")),this.$.tooltip.classList.remove("cancel-animation"),this.$.tooltip.classList.add("hidden")},_onAnimationFinish:function(){this._showing&&(this.$.tooltip.classList.remove(this._getAnimationType("entry")),this.$.tooltip.classList.remove("cancel-animation"),this.$.tooltip.classList.add(this._getAnimationType("exit")))},_onAnimationEnd:function(){this._animationPlaying=!1,this._showing||(this.$.tooltip.classList.remove(this._getAnimationType("exit")),this.$.tooltip.classList.add("hidden"))},_getAnimationType:function(t){if("entry"===t&&""!==this.animationEntry)return this.animationEntry;if("exit"===t&&""!==this.animationExit)return this.animationExit;if(this.animationConfig[t]&&"string"==typeof this.animationConfig[t][0].name){if(this.animationConfig[t][0].timing&&this.animationConfig[t][0].timing.delay&&0!==this.animationConfig[t][0].timing.delay){var i=this.animationConfig[t][0].timing.delay;"entry"===t?this.updateStyles({"--paper-tooltip-delay-in":i+"ms"}):"exit"===t&&this.updateStyles({"--paper-tooltip-delay-out":i+"ms"})}return this.animationConfig[t][0].name}},_removeListeners:function(){this._target&&(this.unlisten(this._target,"mouseenter","show"),this.unlisten(this._target,"focus","show"),this.unlisten(this._target,"mouseleave","hide"),this.unlisten(this._target,"blur","hide"),this.unlisten(this._target,"tap","hide")),this.unlisten(this.$.tooltip,"animationend","_onAnimationEnd"),this.unlisten(this,"mouseenter","hide")}});
|
||||
@@ -0,0 +1 @@
|
||||
const e=()=>{const e={},r=new URLSearchParams(location.search);for(const[n,t]of r.entries())e[n]=t;return e},r=e=>{const r=new URLSearchParams;return Object.entries(e).forEach((([e,n])=>{r.append(e,n)})),r.toString()};export{r as c,e};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
import{a as t,h as e,e as r,$ as n,ah as i,ai as a,r as o,n as s}from"./main-ad130be7.js";t([s("hacs-link")],(function(t,e){return{F:class extends e{constructor(...e){super(...e),t(this)}},d:[{kind:"field",decorators:[r({type:Boolean})],key:"newtab",value:()=>!1},{kind:"field",decorators:[r({type:Boolean})],key:"parent",value:()=>!1},{kind:"field",decorators:[r()],key:"title",value:()=>""},{kind:"field",decorators:[r()],key:"url",value:void 0},{kind:"method",key:"render",value:function(){return n`<span title=${this.title||this.url} @click=${this._open}><slot></slot></span>`}},{kind:"method",key:"_open",value:function(){var t;if(this.url.startsWith("/")&&!this.newtab)return void i(this.url,{replace:!0});const e=null===(t=this.url)||void 0===t?void 0:t.startsWith("http");let r="",n="_blank";e&&(r="noreferrer=true"),e||this.newtab||(n="_blank"),e||this.parent||(n="_parent"),a.open(this.url,n,r)}},{kind:"get",static:!0,key:"styles",value:function(){return o`
|
||||
span {
|
||||
cursor: pointer;
|
||||
color: var(--hcv-text-color-link);
|
||||
text-decoration: var(--hcv-text-decoration-link);
|
||||
}
|
||||
`}}]}}),e);
|
||||
@@ -0,0 +1 @@
|
||||
var a=[];export{a as default};
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function o(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function t(e,o){return e(o={exports:{}},o.exports),o.exports}function n(e){return e&&e.default||e}export{e as a,t as c,n as g,o as u};
|
||||
@@ -0,0 +1,121 @@
|
||||
import{_ as e,n as t,a as i,H as a,e as s,b as r,m as o,$ as l,o as c,c as d,s as n,d as h,r as u}from"./main-ad130be7.js";import"./c.82eccc94.js";import{s as p,S as f,a as m}from"./c.42d6aebd.js";import"./c.f1291e50.js";import"./c.9b92f489.js";import"./c.11ad1623.js";import{f as v}from"./c.3243a8b0.js";import{b as g}from"./c.0a1cf8d0.js";import"./c.a5f69ed4.js";import"./c.82e03b89.js";import"./c.8e28b461.js";import"./c.3f859915.js";import"./c.710a50fc.js";let y=class extends f{};y.styles=[p],y=e([t("mwc-select")],y);const _=["stars","last_updated","name"];let k=i([t("hacs-add-repository-dialog")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[s({attribute:!1})],key:"filters",value:()=>[]},{kind:"field",decorators:[s({type:Number})],key:"_load",value:()=>30},{kind:"field",decorators:[s({type:Number})],key:"_top",value:()=>0},{kind:"field",decorators:[s()],key:"_searchInput",value:()=>""},{kind:"field",decorators:[s()],key:"_sortBy",value:()=>_[0]},{kind:"field",decorators:[s()],key:"section",value:void 0},{kind:"method",key:"shouldUpdate",value:function(e){return e.forEach(((e,t)=>{"hass"===t&&(this.sidebarDocked='"docked"'===window.localStorage.getItem("dockedSidebar"))})),e.has("narrow")||e.has("filters")||e.has("active")||e.has("_searchInput")||e.has("_load")||e.has("_sortBy")}},{kind:"field",key:"_repositoriesInActiveCategory",value(){return(e,t)=>null==e?void 0:e.filter((e=>{var i,a;return!e.installed&&(null===(i=this.hacs.sections)||void 0===i||null===(a=i.find((e=>e.id===this.section)).categories)||void 0===a?void 0:a.includes(e.category))&&!e.installed&&(null==t?void 0:t.includes(e.category))}))}},{kind:"method",key:"firstUpdated",value:async function(){var e;if(this.addEventListener("filter-change",(e=>this._updateFilters(e))),0===(null===(e=this.filters)||void 0===e?void 0:e.length)){var t;const e=null===(t=r(this.hacs.language,this.route))||void 0===t?void 0:t.categories;null==e||e.filter((e=>{var t;return null===(t=this.hacs.info)||void 0===t?void 0:t.categories.includes(e)})).forEach((e=>{this.filters.push({id:e,value:e,checked:!0})})),this.requestUpdate("filters")}}},{kind:"method",key:"_updateFilters",value:function(e){const t=this.filters.find((t=>t.id===e.detail.id));this.filters.find((e=>e.id===t.id)).checked=!t.checked,this.requestUpdate("filters")}},{kind:"field",key:"_filterRepositories",value:()=>o(v)},{kind:"method",key:"render",value:function(){var e;if(!this.active)return l``;this._searchInput=window.localStorage.getItem("hacs-search")||"";let t=this._filterRepositories(this._repositoriesInActiveCategory(this.repositories,null===(e=this.hacs.info)||void 0===e?void 0:e.categories),this._searchInput);return 0!==this.filters.length&&(t=t.filter((e=>{var t;return null===(t=this.filters.find((t=>t.id===e.category)))||void 0===t?void 0:t.checked}))),l`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.hass=${this.hass}
|
||||
.title=${this.hacs.localize("dialog_add_repo.title")}
|
||||
hideActions
|
||||
scrimClickAction
|
||||
maxWidth
|
||||
>
|
||||
<div class="searchandfilter" ?narrow=${this.narrow}>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
.label=${this.hacs.localize("search.placeholder")}
|
||||
.filter=${this._searchInput}
|
||||
@value-changed=${this._inputValueChanged}
|
||||
?narrow=${this.narrow}
|
||||
></search-input>
|
||||
<mwc-select
|
||||
?narrow=${this.narrow}
|
||||
.label=${this.hacs.localize("dialog_add_repo.sort_by")}
|
||||
.value=${this._sortBy}
|
||||
@selected=${e=>this._sortBy=e.currentTarget.value}
|
||||
@closed=${m}
|
||||
>
|
||||
${_.map((e=>l`<mwc-list-item .value=${e}>
|
||||
${this.hacs.localize(`dialog_add_repo.sort_by_values.${e}`)||e}
|
||||
</mwc-list-item>`))}
|
||||
</mwc-select>
|
||||
</div>
|
||||
${this.filters.length>1?l`<div class="filters">
|
||||
<hacs-filter .hacs=${this.hacs} .filters="${this.filters}"></hacs-filter>
|
||||
</div>`:""}
|
||||
<div class=${c({content:!0,narrow:this.narrow})} @scroll=${this._loadMore}>
|
||||
<mwc-list>
|
||||
${0===t.length?l`<ha-alert>${this.hacs.localize("dialog_add_repo.no_match")}</ha-alert>`:t.sort(((e,t)=>"name"===this._sortBy?e.name.toLocaleLowerCase()<t.name.toLocaleLowerCase()?-1:1:e[this._sortBy]>t[this._sortBy]?-1:1)).slice(0,this._load).map((e=>l`<ha-clickable-list-item
|
||||
graphic=${this.narrow?"":"avatar"}
|
||||
twoline
|
||||
@click=${()=>this.active=!1}
|
||||
href="/hacs/repository/${e.id}"
|
||||
.hasMeta=${!this.narrow&&"integration"!==e.category}
|
||||
>
|
||||
${this.narrow?"":"integration"===e.category?l`
|
||||
<img
|
||||
loading="lazy"
|
||||
.src=${g({domain:e.domain,darkOptimized:this.hass.themes.darkMode,type:"icon"})}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
slot="graphic"
|
||||
/>
|
||||
`:l`
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
path="${d}"
|
||||
style="padding-left: 0; height: 40px; width: 40px;"
|
||||
>
|
||||
</ha-svg-icon>
|
||||
`}
|
||||
<span>${e.name}</span>
|
||||
<span slot="secondary">${e.description}</span>
|
||||
<ha-chip slot="meta">
|
||||
${this.hacs.localize(`common.${e.category}`)}
|
||||
</ha-chip>
|
||||
</ha-clickable-list-item>`))}
|
||||
</mwc-list>
|
||||
</div>
|
||||
</hacs-dialog>
|
||||
`}},{kind:"method",key:"_loadMore",value:function(e){const t=e.target.scrollTop;t>=this._top?this._load+=1:this._load-=1,this._top=t}},{kind:"method",key:"_inputValueChanged",value:function(e){this._searchInput=e.detail.value,window.localStorage.setItem("hacs-search",this._searchInput)}},{kind:"method",key:"_onImageLoad",value:function(e){e.target.style.visibility="initial"}},{kind:"method",key:"_onImageError",value:function(e){var t;if(null!==(t=e.target)&&void 0!==t&&t.outerHTML)try{e.target.outerHTML=`<ha-svg-icon path="${d}" slot="graphic"></ha-svg-icon>`}catch(e){}}},{kind:"get",static:!0,key:"styles",value:function(){return[n,h,u`
|
||||
.content {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
.filter {
|
||||
margin-top: -12px;
|
||||
display: flex;
|
||||
width: 200px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 16px;
|
||||
width: 1024px;
|
||||
max-width: 100%;
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 75%;
|
||||
}
|
||||
search-input[narrow],
|
||||
mwc-select[narrow] {
|
||||
width: 100%;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.filters {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
hacs-filter {
|
||||
width: 100%;
|
||||
margin-left: -32px;
|
||||
}
|
||||
|
||||
.searchandfilter {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: self-end;
|
||||
}
|
||||
|
||||
.searchandfilter[narrow] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
margin-left: -52px;
|
||||
}
|
||||
`]}}]}}),a);export{k as HacsAddRepositoryDialog};
|
||||
@@ -0,0 +1,178 @@
|
||||
import{a as o,h as t,e,$ as i,w as a,r as s,n as r,t as n,L as h,N as l,ai as c,a2 as d,a3 as p,m as u,c as y,aN as m,aO as f,aP as v,E as _,aQ as g,z as b,aR as k,aS as w,aT as $,aU as x,aV as z,aW as j,ah as R,am as L,ar as S,as as F,aX as I,d as P}from"./main-ad130be7.js";import"./c.bc5a73e9.js";import{e as C}from"./c.50bfd408.js";import"./c.97b7c4b0.js";import{r as E}from"./c.4204ca09.js";import{g as T}from"./c.f2bb3724.js";import{s as U}from"./c.4266acdb.js";import"./c.a5f69ed4.js";import{f as D}from"./c.fe747ba2.js";import{m as K}from"./c.f6611997.js";import"./c.2d5ed670.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.4feb0cb8.js";import"./c.0ca5587f.js";import"./c.5d3ce9d6.js";import"./c.743a15a1.js";o([r("hass-subpage")],(function(o,t){return{F:class extends t{constructor(...t){super(...t),o(this)}},d:[{kind:"field",decorators:[e({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[e()],key:"header",value:void 0},{kind:"field",decorators:[e({type:Boolean,attribute:"main-page"})],key:"mainPage",value:()=>!1},{kind:"field",decorators:[e({type:String,attribute:"back-path"})],key:"backPath",value:void 0},{kind:"field",decorators:[e({type:Boolean,reflect:!0})],key:"narrow",value:()=>!1},{kind:"field",decorators:[e({type:Boolean})],key:"supervisor",value:()=>!1},{kind:"field",decorators:[E(".content")],key:"_savedScrollPos",value:void 0},{kind:"method",key:"render",value:function(){var o;return i`
|
||||
<div class="toolbar">
|
||||
${this.mainPage||null!==(o=history.state)&&void 0!==o&&o.root?i`
|
||||
<ha-menu-button
|
||||
.hassio=${this.supervisor}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
`:this.backPath?i`
|
||||
<a href=${this.backPath}>
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
></ha-icon-button-arrow-prev>
|
||||
</a>
|
||||
`:i`
|
||||
<ha-icon-button-arrow-prev
|
||||
.hass=${this.hass}
|
||||
@click=${this._backTapped}
|
||||
></ha-icon-button-arrow-prev>
|
||||
`}
|
||||
|
||||
<div class="main-title">${this.header}</div>
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
<div class="content" @scroll=${this._saveScrollPos}><slot></slot></div>
|
||||
`}},{kind:"method",decorators:[a({passive:!0})],key:"_saveScrollPos",value:function(o){this._savedScrollPos=o.target.scrollTop}},{kind:"method",key:"_backTapped",value:function(){history.back()}},{kind:"get",static:!0,key:"styles",value:function(){return s`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
padding: 0 16px;
|
||||
pointer-events: none;
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: 400;
|
||||
color: var(--app-header-text-color, white);
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
.main-title {
|
||||
margin: 0 0 0 24px;
|
||||
line-height: 20px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 1px - var(--header-height));
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
`}}]}}),t);let M=o([r("hacs-repository-panel")],(function(o,t){class a extends t{constructor(...t){super(...t),o(this)}}return{F:a,d:[{kind:"field",decorators:[e({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"narrow",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"isWide",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"_repository",value:void 0},{kind:"field",decorators:[n()],key:"_error",value:void 0},{kind:"method",key:"connectedCallback",value:function(){h(l(a.prototype),"connectedCallback",this).call(this),document.body.addEventListener("keydown",this._generateMyLink)}},{kind:"method",key:"disconnectedCallback",value:function(){h(l(a.prototype),"disconnectedCallback",this).call(this),document.body.removeEventListener("keydown",this._generateMyLink)}},{kind:"field",key:"_generateMyLink",value(){return o=>{if(!(o.ctrlKey||o.shiftKey||o.metaKey||o.altKey)&&"m"===o.key&&c.location.pathname.startsWith("/hacs/repository/")){if(!this._repository)return;const o=new URLSearchParams({redirect:"hacs_repository",owner:this._repository.full_name.split("/")[0],repository:this._repository.full_name.split("/")[1],category:this._repository.category});window.open(`https://my.home-assistant.io/create-link/?${o.toString()}`,"_blank")}}}},{kind:"method",key:"firstUpdated",value:async function(o){h(l(a.prototype),"firstUpdated",this).call(this,o);const t=C();if(Object.entries(t).length){let o;const e=`${t.owner}/${t.repository}`;if(o=this.hacs.repositories.find((o=>o.full_name.toLocaleLowerCase()===e.toLocaleLowerCase())),!o&&t.category){if(!await U(this,{title:this.hacs.localize("my.add_repository_title"),text:this.hacs.localize("my.add_repository_description",{repository:e}),confirmText:this.hacs.localize("common.add"),dismissText:this.hacs.localize("common.cancel")}))return void(this._error=this.hacs.localize("my.repository_not_found",{repository:e}));try{await d(this.hass,e,t.category),this.hacs.repositories=await p(this.hass),o=this.hacs.repositories.find((o=>o.full_name.toLocaleLowerCase()===e.toLocaleLowerCase()))}catch(o){return void(this._error=o)}}o?this._fetchRepository(String(o.id)):this._error=this.hacs.localize("my.repository_not_found",{repository:e})}else{const o=this.route.path.indexOf("/",1),t=this.route.path.substr(o+1);if(!t)return void(this._error="Missing repositoryId from route");this._fetchRepository(t)}}},{kind:"method",key:"updated",value:function(o){h(l(a.prototype),"updated",this).call(this,o),o.has("repositories")&&this._repository&&this._fetchRepository()}},{kind:"method",key:"_fetchRepository",value:async function(o){try{this._repository=await D(this.hass,o||String(this._repository.id))}catch(o){this._error=null==o?void 0:o.message}}},{kind:"field",key:"_getAuthors",value:()=>u((o=>{const t=[];if(!o.authors)return t;if(o.authors.forEach((o=>t.push(o.replace("@","")))),0===t.length){const e=o.full_name.split("/")[0];if(["custom-cards","custom-components","home-assistant-community-themes"].includes(e))return t;t.push(e)}return t}))},{kind:"method",key:"render",value:function(){if(this._error)return i`<hass-error-screen .error=${this._error}></hass-error-screen>`;if(!this._repository)return i`<hass-loading-screen></hass-loading-screen>`;const o=this._getAuthors(this._repository);return i`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.header=${this._repository.name}
|
||||
hasFab
|
||||
>
|
||||
<ha-icon-overflow-menu
|
||||
slot="toolbar-icon"
|
||||
narrow
|
||||
.hass=${this.hass}
|
||||
.items=${[{path:y,label:this.hacs.localize("common.repository"),action:()=>c.open(`https://github.com/${this._repository.full_name}`,"_blank","noreferrer=true")},{path:m,label:this.hacs.localize("repository_card.update_information"),action:()=>this._refreshReopsitoryInfo()},{path:f,label:this.hacs.localize("repository_card.redownload"),action:()=>this._downloadRepositoryDialog(),hideForUninstalled:!0},{category:"plugin",hideForUninstalled:!0,path:v,label:this.hacs.localize("repository_card.open_source"),action:()=>c.open(`/hacsfiles/${this._repository.local_path.split("/").pop()}/${this._repository.file_name}`,"_blank","noreferrer=true")},{path:_,label:this.hacs.localize("repository_card.open_issue"),action:()=>c.open(`https://github.com/${this._repository.full_name}/issues`,"_blank","noreferrer=true")},{hideForId:"172733314",path:g,label:this.hacs.localize("repository_card.report"),hideForUninstalled:!0,action:()=>c.open(`https://github.com/hacs/integration/issues/new?assignees=ludeeus&labels=flag&template=removal.yml&repo=${this._repository.full_name}&title=Request for removal of ${this._repository.full_name}`,"_blank","noreferrer=true")},{hideForId:"172733314",hideForUninstalled:!0,path:b,label:this.hacs.localize("common.remove"),action:()=>this._removeRepositoryDialog()}].filter((o=>(!o.category||this._repository.category===o.category)&&(!o.hideForId||String(this._repository.id)!==o.hideForId)&&(!o.hideForUninstalled||this._repository.installed_version)))}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
<div class="content">
|
||||
<div class="chips">
|
||||
${this._repository.installed?i`
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.version_installed")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${k}></ha-svg-icon>
|
||||
${this._repository.installed_version}
|
||||
</ha-chip>
|
||||
`:""}
|
||||
${o?o.map((o=>i`<hacs-link .url="https://github.com/${o}">
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.author")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${w}></ha-svg-icon>
|
||||
@${o}
|
||||
</ha-chip>
|
||||
</hacs-link>`)):""}
|
||||
${this._repository.downloads?i` <ha-chip hasIcon title="${this.hacs.localize("dialog_info.downloads")}">
|
||||
<ha-svg-icon slot="icon" .path=${$}></ha-svg-icon>
|
||||
${this._repository.downloads}
|
||||
</ha-chip>`:""}
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.stars")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${x}></ha-svg-icon>
|
||||
${this._repository.stars}
|
||||
</ha-chip>
|
||||
<hacs-link .url="https://github.com/${this._repository.full_name}/issues">
|
||||
<ha-chip title="${this.hacs.localize("dialog_info.open_issues")}" hasIcon>
|
||||
<ha-svg-icon slot="icon" .path=${z}></ha-svg-icon>
|
||||
${this._repository.issues}
|
||||
</ha-chip>
|
||||
</hacs-link>
|
||||
</div>
|
||||
${K.html(this._repository.additional_info||this.hacs.localize("dialog_info.no_info"),this._repository)}
|
||||
</div>
|
||||
${this._repository.installed_version?"":i`<ha-fab
|
||||
.label=${this.hacs.localize("common.download")}
|
||||
.extended=${!this.narrow}
|
||||
@click=${this._downloadRepositoryDialog}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${j}></ha-svg-icon>
|
||||
</ha-fab>`}
|
||||
</hass-subpage>
|
||||
`}},{kind:"method",key:"_downloadRepositoryDialog",value:function(){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"download",repository:this._repository.id},bubbles:!0,composed:!0}))}},{kind:"method",key:"_removeRepositoryDialog",value:async function(){if("integration"===this._repository.category&&this._repository.config_flow){if((await T(this.hass)).some((o=>o.domain===this._repository.domain))){if(await U(this,{title:this.hacs.localize("dialog.configured.title"),text:this.hacs.localize("dialog.configured.message",{name:this._repository.name}),dismissText:this.hacs.localize("common.ignore"),confirmText:this.hacs.localize("common.navigate"),confirm:()=>{R("/config/integrations",{replace:!0})}}))return}}this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"progress",title:this.hacs.localize("dialog.remove.title"),confirmText:this.hacs.localize("dialog.remove.title"),content:this.hacs.localize("dialog.remove.message",{name:this._repository.name}),confirm:async()=>{await this._repositoryRemove()}},bubbles:!0,composed:!0}))}},{kind:"method",key:"_repositoryRemove",value:async function(){var o;if("plugin"===this._repository.category&&"yaml"!==(null===(o=this.hacs.info)||void 0===o?void 0:o.lovelace_mode)){(await L(this.hass)).filter((o=>o.url.startsWith(`/hacsfiles/${this._repository.full_name.split("/")[1]}/${this._repository.file_name}`))).forEach((async o=>{await S(this.hass,String(o.id))}))}await F(this.hass,String(this._repository.id)),history.back()}},{kind:"method",key:"_refreshReopsitoryInfo",value:async function(){await I(this.hass,String(this._repository.id))}},{kind:"get",static:!0,key:"styles",value:function(){return[P,s`
|
||||
hass-loading-screen {
|
||||
--app-header-background-color: var(--sidebar-background-color);
|
||||
--app-header-text-color: var(--sidebar-text-color);
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
hass-subpage {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
color: var(--hcv-text-color-on-background);
|
||||
}
|
||||
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
float: right;
|
||||
right: calc(18px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ha-fab.rtl {
|
||||
float: left;
|
||||
right: auto;
|
||||
left: calc(18px + env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 12px;
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: 8px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 500px) {
|
||||
.content {
|
||||
margin: 8px 4px 64px;
|
||||
}
|
||||
}
|
||||
`]}}]}}),t);export{M as HacsRepositoryPanel};
|
||||
@@ -0,0 +1,108 @@
|
||||
import{a as s,H as i,e,t,L as a,N as o,at as r,a0 as n,a1 as l,$ as c,o as h,au as d,ai as p,s as m,d as _,r as v,n as u}from"./main-ad130be7.js";import{c as y}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.2ee83bd0.js";import{s as g}from"./c.4266acdb.js";import{f,a as $}from"./c.fe747ba2.js";import{m as b}from"./c.f6611997.js";import{u as x}from"./c.25ed1ae4.js";import"./c.5d3ce9d6.js";import"./c.82e03b89.js";import"./c.743a15a1.js";import"./c.710a50fc.js";import"./c.8e28b461.js";let k=s([u("hacs-update-dialog")],(function(s,i){class u extends i{constructor(...i){super(...i),s(this)}}return{F:u,d:[{kind:"field",decorators:[e()],key:"repository",value:void 0},{kind:"field",decorators:[e({type:Boolean})],key:"_updating",value:()=>!1},{kind:"field",decorators:[e()],key:"_error",value:void 0},{kind:"field",decorators:[e({attribute:!1})],key:"_releaseNotes",value:()=>[]},{kind:"field",decorators:[t()],key:"_repository",value:void 0},{kind:"method",key:"firstUpdated",value:async function(s){a(o(u.prototype),"firstUpdated",this).call(this,s),this._repository=await f(this.hass,this.repository),this._repository&&("commit"!==this._repository.version_or_commit&&(this._releaseNotes=await r(this.hass,String(this._repository.id))),n(this.hass,(s=>this._error=s),l.ERROR))}},{kind:"method",key:"render",value:function(){var s;return this.active&&this._repository?c`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.title=${this.hacs.localize("dialog_update.title")}
|
||||
.hass=${this.hass}
|
||||
>
|
||||
<div class=${h({content:!0,narrow:this.narrow})}>
|
||||
<p class="message">
|
||||
${this.hacs.localize("dialog_update.message",{name:this._repository.name})}
|
||||
</p>
|
||||
<div class="version-container">
|
||||
<div class="version-element">
|
||||
<span class="version-number">${this._repository.installed_version}</span>
|
||||
<small class="version-text">${this.hacs.localize("dialog_update.downloaded_version")}</small>
|
||||
</div>
|
||||
|
||||
<span class="version-separator">
|
||||
<ha-svg-icon
|
||||
.path=${d}
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
|
||||
<div class="version-element">
|
||||
<span class="version-number">${this._repository.available_version}</span>
|
||||
<small class="version-text">${this.hacs.localize("dialog_update.available_version")}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this._releaseNotes.length>0?this._releaseNotes.map((s=>c`
|
||||
<ha-expansion-panel
|
||||
.header=${s.name&&s.name!==s.tag?`${s.tag}: ${s.name}`:s.tag}
|
||||
outlined
|
||||
?expanded=${1===this._releaseNotes.length}
|
||||
>
|
||||
${s.body?b.html(s.body,this._repository):this.hacs.localize("dialog_update.no_info")}
|
||||
</ha-expansion-panel>
|
||||
`)):""}
|
||||
${this._repository.can_download?"":c`<ha-alert alert-type="error" .rtl=${y(this.hass)}>
|
||||
${this.hacs.localize("confirm.home_assistant_version_not_correct",{haversion:this.hass.config.version,minversion:this._repository.homeassistant})}
|
||||
</ha-alert>`}
|
||||
${"integration"===this._repository.category?c`<p>${this.hacs.localize("dialog_download.restart")}</p>`:""}
|
||||
${null!==(s=this._error)&&void 0!==s&&s.message?c`<ha-alert alert-type="error" .rtl=${y(this.hass)}>
|
||||
${this._error.message}
|
||||
</ha-alert>`:""}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryaction"
|
||||
?disabled=${!this._repository.can_download}
|
||||
@click=${this._updateRepository}
|
||||
raised
|
||||
>
|
||||
${this._updating?c`<ha-circular-progress active size="small"></ha-circular-progress>`:this.hacs.localize("common.update")}
|
||||
</mwc-button
|
||||
>
|
||||
<div class="secondary" slot="secondaryaction">
|
||||
<hacs-link .url=${this._getChanglogURL()||""}>
|
||||
<mwc-button>${this.hacs.localize("dialog_update.changelog")}
|
||||
</mwc-button>
|
||||
</hacs-link>
|
||||
<hacs-link .url="https://github.com/${this._repository.full_name}">
|
||||
<mwc-button>${this.hacs.localize("common.repository")}
|
||||
</mwc-button>
|
||||
</hacs-link>
|
||||
</div>
|
||||
</hacs-dialog>
|
||||
`:c``}},{kind:"method",key:"_updateRepository",value:async function(){this._updating=!0,"commit"!==this._repository.version_or_commit?await $(this.hass,String(this._repository.id),this._repository.available_version):await $(this.hass,String(this._repository.id)),"plugin"===this._repository.category&&"storage"===this.hacs.info.lovelace_mode&&await x(this.hass,this._repository,this._repository.available_version),this._updating=!1,this.dispatchEvent(new Event("hacs-dialog-closed",{bubbles:!0,composed:!0})),"plugin"===this._repository.category&&g(this,{title:this.hacs.localize("common.reload"),text:c`${this.hacs.localize("dialog.reload.description")}<br />${this.hacs.localize("dialog.reload.confirm")}`,dismissText:this.hacs.localize("common.cancel"),confirmText:this.hacs.localize("common.reload"),confirm:()=>{p.location.href=p.location.href}})}},{kind:"method",key:"_getChanglogURL",value:function(){return"commit"===this._repository.version_or_commit?`https://github.com/${this._repository.full_name}/compare/${this._repository.installed_version}...${this._repository.available_version}`:`https://github.com/${this._repository.full_name}/releases`}},{kind:"get",static:!0,key:"styles",value:function(){return[m,_,v`
|
||||
.content {
|
||||
width: 360px;
|
||||
display: contents;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
margin: 8px 0;
|
||||
}
|
||||
ha-expansion-panel[expanded] {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
display: flex;
|
||||
}
|
||||
.message {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
.version-container {
|
||||
margin: 24px 0 12px 0;
|
||||
width: 360px;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.version-element {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 0 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.version-text {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.version-number {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
`]}}]}}),i);export{k as HacsUpdateDialog};
|
||||
@@ -0,0 +1,17 @@
|
||||
import{a as e,H as i,e as t,$ as o,P as s,d as a,r as c,n}from"./main-ad130be7.js";import{c as l}from"./c.710a50fc.js";e([n("hacs-dialog")],(function(e,i){return{F:class extends i{constructor(...i){super(...i),e(this)}},d:[{kind:"field",decorators:[t({type:Boolean})],key:"hideActions",value:()=>!1},{kind:"field",decorators:[t({type:Boolean})],key:"scrimClickAction",value:()=>!1},{kind:"field",decorators:[t({type:Boolean})],key:"escapeKeyAction",value:()=>!1},{kind:"field",decorators:[t({type:Boolean})],key:"noClose",value:()=>!1},{kind:"field",decorators:[t({type:Boolean})],key:"maxWidth",value:()=>!1},{kind:"field",decorators:[t()],key:"title",value:void 0},{kind:"method",key:"render",value:function(){return this.active?o`<ha-dialog
|
||||
?maxWidth=${this.maxWidth}
|
||||
?open=${this.active}
|
||||
?scrimClickAction=${this.scrimClickAction}
|
||||
?escapeKeyAction=${this.escapeKeyAction}
|
||||
@closed=${this.closeDialog}
|
||||
?hideActions=${this.hideActions}
|
||||
.heading=${this.noClose?this.title:l(this.hass,this.title)}
|
||||
>
|
||||
<slot></slot>
|
||||
<slot class="primary" name="primaryaction" slot="primaryAction"></slot>
|
||||
<slot class="secondary" name="secondaryaction" slot="secondaryAction"></slot>
|
||||
</ha-dialog>`:o``}},{kind:"method",key:"closeDialog",value:function(){this.active=!1,this.dispatchEvent(new CustomEvent("closed",{bubbles:!0,composed:!0}))}},{kind:"get",static:!0,key:"styles",value:function(){return[s,a,c`
|
||||
ha-dialog[maxWidth] {
|
||||
--mdc-dialog-max-width: calc(100vw - 32px);
|
||||
}
|
||||
`]}}]}}),i);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,50 @@
|
||||
import{u as e,v as t,M as c,_ as i,e as r,K as s,i as o,g as d,t as a,w as n,B as h,R as l,y as p,$ as u,j as m,r as b,A as w,a as v,L as k,N as f,n as _}from"./main-ad130be7.js";import{o as y}from"./c.8e28b461.js";var g={CHECKED:"mdc-switch--checked",DISABLED:"mdc-switch--disabled"},C={ARIA_CHECKED_ATTR:"aria-checked",NATIVE_CONTROL_SELECTOR:".mdc-switch__native-control",RIPPLE_SURFACE_SELECTOR:".mdc-switch__thumb-underlay"},x=function(c){function i(e){return c.call(this,t(t({},i.defaultAdapter),e))||this}return e(i,c),Object.defineProperty(i,"strings",{get:function(){return C},enumerable:!1,configurable:!0}),Object.defineProperty(i,"cssClasses",{get:function(){return g},enumerable:!1,configurable:!0}),Object.defineProperty(i,"defaultAdapter",{get:function(){return{addClass:function(){},removeClass:function(){},setNativeControlChecked:function(){},setNativeControlDisabled:function(){},setNativeControlAttr:function(){}}},enumerable:!1,configurable:!0}),i.prototype.setChecked=function(e){this.adapter.setNativeControlChecked(e),this.updateAriaChecked(e),this.updateCheckedStyling(e)},i.prototype.setDisabled=function(e){this.adapter.setNativeControlDisabled(e),e?this.adapter.addClass(g.DISABLED):this.adapter.removeClass(g.DISABLED)},i.prototype.handleChange=function(e){var t=e.target;this.updateAriaChecked(t.checked),this.updateCheckedStyling(t.checked)},i.prototype.updateCheckedStyling=function(e){e?this.adapter.addClass(g.CHECKED):this.adapter.removeClass(g.CHECKED)},i.prototype.updateAriaChecked=function(e){this.adapter.setNativeControlAttr(C.ARIA_CHECKED_ATTR,""+!!e)},i}(c);class R extends h{constructor(){super(...arguments),this.checked=!1,this.disabled=!1,this.shouldRenderRipple=!1,this.mdcFoundationClass=x,this.rippleHandlers=new l((()=>(this.shouldRenderRipple=!0,this.ripple)))}changeHandler(e){this.mdcFoundation.handleChange(e),this.checked=this.formElement.checked}createAdapter(){return Object.assign(Object.assign({},p(this.mdcRoot)),{setNativeControlChecked:e=>{this.formElement.checked=e},setNativeControlDisabled:e=>{this.formElement.disabled=e},setNativeControlAttr:(e,t)=>{this.formElement.setAttribute(e,t)}})}renderRipple(){return this.shouldRenderRipple?u`
|
||||
<mwc-ripple
|
||||
.accent="${this.checked}"
|
||||
.disabled="${this.disabled}"
|
||||
unbounded>
|
||||
</mwc-ripple>`:""}focus(){const e=this.formElement;e&&(this.rippleHandlers.startFocus(),e.focus())}blur(){const e=this.formElement;e&&(this.rippleHandlers.endFocus(),e.blur())}click(){this.formElement&&!this.disabled&&(this.formElement.focus(),this.formElement.click())}firstUpdated(){super.firstUpdated(),this.shadowRoot&&this.mdcRoot.addEventListener("change",(e=>{this.dispatchEvent(new Event("change",e))}))}render(){return u`
|
||||
<div class="mdc-switch">
|
||||
<div class="mdc-switch__track"></div>
|
||||
<div class="mdc-switch__thumb-underlay">
|
||||
${this.renderRipple()}
|
||||
<div class="mdc-switch__thumb">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="basic-switch"
|
||||
class="mdc-switch__native-control"
|
||||
role="switch"
|
||||
aria-label="${m(this.ariaLabel)}"
|
||||
aria-labelledby="${m(this.ariaLabelledBy)}"
|
||||
@change="${this.changeHandler}"
|
||||
@focus="${this.handleRippleFocus}"
|
||||
@blur="${this.handleRippleBlur}"
|
||||
@mousedown="${this.handleRippleMouseDown}"
|
||||
@mouseenter="${this.handleRippleMouseEnter}"
|
||||
@mouseleave="${this.handleRippleMouseLeave}"
|
||||
@touchstart="${this.handleRippleTouchStart}"
|
||||
@touchend="${this.handleRippleDeactivate}"
|
||||
@touchcancel="${this.handleRippleDeactivate}">
|
||||
</div>
|
||||
</div>
|
||||
</div>`}handleRippleMouseDown(e){const t=()=>{window.removeEventListener("mouseup",t),this.handleRippleDeactivate()};window.addEventListener("mouseup",t),this.rippleHandlers.startPress(e)}handleRippleTouchStart(e){this.rippleHandlers.startPress(e)}handleRippleDeactivate(){this.rippleHandlers.endPress()}handleRippleMouseEnter(){this.rippleHandlers.startHover()}handleRippleMouseLeave(){this.rippleHandlers.endHover()}handleRippleFocus(){this.rippleHandlers.startFocus()}handleRippleBlur(){this.rippleHandlers.endFocus()}}i([r({type:Boolean}),y((function(e){this.mdcFoundation.setChecked(e)}))],R.prototype,"checked",void 0),i([r({type:Boolean}),y((function(e){this.mdcFoundation.setDisabled(e)}))],R.prototype,"disabled",void 0),i([s,r({attribute:"aria-label"})],R.prototype,"ariaLabel",void 0),i([s,r({attribute:"aria-labelledby"})],R.prototype,"ariaLabelledBy",void 0),i([o(".mdc-switch")],R.prototype,"mdcRoot",void 0),i([o("input")],R.prototype,"formElement",void 0),i([d("mwc-ripple")],R.prototype,"ripple",void 0),i([a()],R.prototype,"shouldRenderRipple",void 0),i([n({passive:!0})],R.prototype,"handleRippleMouseDown",null),i([n({passive:!0})],R.prototype,"handleRippleTouchStart",null);const E=b`.mdc-switch__thumb-underlay{left:-14px;right:initial;top:-17px;width:48px;height:48px}[dir=rtl] .mdc-switch__thumb-underlay,.mdc-switch__thumb-underlay[dir=rtl]{left:initial;right:-14px}.mdc-switch__native-control{width:64px;height:48px}.mdc-switch{display:inline-block;position:relative;outline:none;user-select:none}.mdc-switch.mdc-switch--checked .mdc-switch__track{background-color:#018786;background-color:var(--mdc-theme-secondary, #018786)}.mdc-switch.mdc-switch--checked .mdc-switch__thumb{background-color:#018786;background-color:var(--mdc-theme-secondary, #018786);border-color:#018786;border-color:var(--mdc-theme-secondary, #018786)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__track{background-color:#000;background-color:var(--mdc-theme-on-surface, #000)}.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb{background-color:#fff;background-color:var(--mdc-theme-surface, #fff);border-color:#fff;border-color:var(--mdc-theme-surface, #fff)}.mdc-switch__native-control{left:0;right:initial;position:absolute;top:0;margin:0;opacity:0;cursor:pointer;pointer-events:auto;transition:transform 90ms cubic-bezier(0.4, 0, 0.2, 1)}[dir=rtl] .mdc-switch__native-control,.mdc-switch__native-control[dir=rtl]{left:initial;right:0}.mdc-switch__track{box-sizing:border-box;width:36px;height:14px;border:1px solid transparent;border-radius:7px;opacity:.38;transition:opacity 90ms cubic-bezier(0.4, 0, 0.2, 1),background-color 90ms cubic-bezier(0.4, 0, 0.2, 1),border-color 90ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-switch__thumb-underlay{display:flex;position:absolute;align-items:center;justify-content:center;transform:translateX(0);transition:transform 90ms cubic-bezier(0.4, 0, 0.2, 1),background-color 90ms cubic-bezier(0.4, 0, 0.2, 1),border-color 90ms cubic-bezier(0.4, 0, 0.2, 1)}.mdc-switch__thumb{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,.12);box-sizing:border-box;width:20px;height:20px;border:10px solid;border-radius:50%;pointer-events:none;z-index:1}.mdc-switch--checked .mdc-switch__track{opacity:.54}.mdc-switch--checked .mdc-switch__thumb-underlay{transform:translateX(16px)}[dir=rtl] .mdc-switch--checked .mdc-switch__thumb-underlay,.mdc-switch--checked .mdc-switch__thumb-underlay[dir=rtl]{transform:translateX(-16px)}.mdc-switch--checked .mdc-switch__native-control{transform:translateX(-16px)}[dir=rtl] .mdc-switch--checked .mdc-switch__native-control,.mdc-switch--checked .mdc-switch__native-control[dir=rtl]{transform:translateX(16px)}.mdc-switch--disabled{opacity:.38;pointer-events:none}.mdc-switch--disabled .mdc-switch__thumb{border-width:1px}.mdc-switch--disabled .mdc-switch__native-control{cursor:default;pointer-events:none}:host{display:inline-flex;outline:none;-webkit-tap-highlight-color:transparent}`;v([_("ha-switch")],(function(e,t){class c extends t{constructor(...t){super(...t),e(this)}}return{F:c,d:[{kind:"field",decorators:[r({type:Boolean})],key:"haptic",value:()=>!1},{kind:"method",key:"firstUpdated",value:function(){k(f(c.prototype),"firstUpdated",this).call(this),this.addEventListener("change",(()=>{this.haptic&&w(window,"haptic","light")}))}},{kind:"field",static:!0,key:"styles",value:()=>[E,b`
|
||||
:host {
|
||||
--mdc-theme-secondary: var(--switch-checked-color);
|
||||
}
|
||||
.mdc-switch.mdc-switch--checked .mdc-switch__thumb {
|
||||
background-color: var(--switch-checked-button-color);
|
||||
border-color: var(--switch-checked-button-color);
|
||||
}
|
||||
.mdc-switch.mdc-switch--checked .mdc-switch__track {
|
||||
background-color: var(--switch-checked-track-color);
|
||||
border-color: var(--switch-checked-track-color);
|
||||
}
|
||||
.mdc-switch:not(.mdc-switch--checked) .mdc-switch__thumb {
|
||||
background-color: var(--switch-unchecked-button-color);
|
||||
border-color: var(--switch-unchecked-button-color);
|
||||
}
|
||||
.mdc-switch:not(.mdc-switch--checked) .mdc-switch__track {
|
||||
background-color: var(--switch-unchecked-track-color);
|
||||
border-color: var(--switch-unchecked-track-color);
|
||||
}
|
||||
`]}]}}),R);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
const r=r=>(s,o)=>{if(s.constructor._observers){if(!s.constructor.hasOwnProperty("_observers")){const r=s.constructor._observers;s.constructor._observers=new Map,r.forEach(((r,o)=>s.constructor._observers.set(o,r)))}}else{s.constructor._observers=new Map;const r=s.updated;s.updated=function(s){r.call(this,s),s.forEach(((r,s)=>{const o=this.constructor._observers.get(s);void 0!==o&&o.call(this,this[s],r)}))}}s.constructor._observers.set(o,r)};export{r as o};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
|
||||
import{a as e,h as i,e as t,$ as r,d as o,r as c,n as d}from"./main-ad130be7.js";import"./c.9b92f489.js";e([d("hacs-filter")],(function(e,i){return{F:class extends i{constructor(...i){super(...i),e(this)}},d:[{kind:"field",decorators:[t({attribute:!1})],key:"filters",value:void 0},{kind:"field",decorators:[t({attribute:!1})],key:"hacs",value:void 0},{kind:"method",key:"render",value:function(){var e;return r`
|
||||
<div class="filter">
|
||||
${null===(e=this.filters)||void 0===e?void 0:e.map((e=>r`
|
||||
<ha-formfield
|
||||
class="checkbox"
|
||||
.label=${this.hacs.localize(`common.${e.id}`)||e.value}
|
||||
.id=${e.id}
|
||||
@click=${this._filterClick}
|
||||
>
|
||||
<ha-checkbox .checked=${e.checked||!1}> </ha-checkbox>
|
||||
</ha-formfield>
|
||||
`))}
|
||||
</div>
|
||||
`}},{kind:"method",key:"_filterClick",value:function(e){const i=e.currentTarget;this.dispatchEvent(new CustomEvent("filter-change",{detail:{id:i.id},bubbles:!0,composed:!0}))}},{kind:"get",static:!0,key:"styles",value:function(){return[o,c`
|
||||
.filter {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
height: 32px;
|
||||
line-height: 4px;
|
||||
background-color: var(--sidebar-background-color);
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.checkbox:not(:first-child) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
`]}}]}}),i);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,6 @@
|
||||
import{a as e,H as t,e as i,m as o,$ as s,n as r}from"./main-ad130be7.js";import{m as a}from"./c.f6611997.js";import"./c.82e03b89.js";import"./c.5d3ce9d6.js";import"./c.743a15a1.js";import"./c.710a50fc.js";import"./c.8e28b461.js";let d=e([r("hacs-generic-dialog")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i({type:Boolean})],key:"markdown",value:()=>!1},{kind:"field",decorators:[i()],key:"repository",value:void 0},{kind:"field",decorators:[i()],key:"header",value:void 0},{kind:"field",decorators:[i()],key:"content",value:void 0},{kind:"field",key:"_getRepository",value:()=>o(((e,t)=>null==e?void 0:e.find((e=>String(e.id)===t))))},{kind:"method",key:"render",value:function(){if(!this.active||!this.repository)return s``;const e=this._getRepository(this.hacs.repositories,this.repository);return s`
|
||||
<hacs-dialog .active=${this.active} .narrow=${this.narrow} .hass=${this.hass}>
|
||||
<div slot="header">${this.header||""}</div>
|
||||
${this.markdown?this.repository?a.html(this.content||"",e):a.html(this.content||""):this.content||""}
|
||||
</hacs-dialog>
|
||||
`}}]}}),t);export{d as HacsGenericDialog};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
|
||||
import{a as i,H as s,e as t,$ as e,d as a,r as o,ap as r,aq as l,am as n,ar as c,as as h,n as d}from"./main-ad130be7.js";import"./c.82e03b89.js";import"./c.710a50fc.js";import"./c.8e28b461.js";let m=i([d("hacs-removed-dialog")],(function(i,s){return{F:class extends s{constructor(...s){super(...s),i(this)}},d:[{kind:"field",decorators:[t({attribute:!1})],key:"repository",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"_updating",value:()=>!1},{kind:"method",key:"render",value:function(){if(!this.active)return e``;const i=this.hacs.removed.find((i=>i.repository===this.repository.full_name));return e`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.hass=${this.hass}
|
||||
.title=${this.hacs.localize("entry.messages.removed_repository",{repository:this.repository.full_name})}
|
||||
>
|
||||
<div class="content">
|
||||
<div><b>${this.hacs.localize("dialog_removed.name")}:</b> ${this.repository.name}</div>
|
||||
${i.removal_type?e` <div>
|
||||
<b>${this.hacs.localize("dialog_removed.type")}:</b> ${i.removal_type}
|
||||
</div>`:""}
|
||||
${i.reason?e` <div>
|
||||
<b>${this.hacs.localize("dialog_removed.reason")}:</b> ${i.reason}
|
||||
</div>`:""}
|
||||
${i.link?e` <div>
|
||||
</b><hacs-link .url=${i.link}>${this.hacs.localize("dialog_removed.link")}</hacs-link>
|
||||
</div>`:""}
|
||||
</div>
|
||||
<mwc-button slot="secondaryaction" @click=${this._ignoreRepository}>
|
||||
${this.hacs.localize("common.ignore")}
|
||||
</mwc-button>
|
||||
<mwc-button class="uninstall" slot="primaryaction" @click=${this._uninstallRepository}
|
||||
>${this._updating?e`<ha-circular-progress active size="small"></ha-circular-progress>`:this.hacs.localize("common.remove")}</mwc-button
|
||||
>
|
||||
</hacs-dialog>
|
||||
`}},{kind:"get",static:!0,key:"styles",value:function(){return[a,o`
|
||||
.uninstall {
|
||||
--mdc-theme-primary: var(--hcv-color-error);
|
||||
}
|
||||
`]}},{kind:"method",key:"_lovelaceUrl",value:function(){var i,s;return`/hacsfiles/${null===(i=this.repository)||void 0===i?void 0:i.full_name.split("/")[1]}/${null===(s=this.repository)||void 0===s?void 0:s.file_name}`}},{kind:"method",key:"_ignoreRepository",value:async function(){await r(this.hass,this.repository.full_name);const i=await l(this.hass);this.dispatchEvent(new CustomEvent("update-hacs",{detail:{removed:i},bubbles:!0,composed:!0})),this.dispatchEvent(new Event("hacs-dialog-closed",{bubbles:!0,composed:!0}))}},{kind:"method",key:"_uninstallRepository",value:async function(){if(this._updating=!0,"plugin"===this.repository.category&&this.hacs.info&&"yaml"!==this.hacs.info.lovelace_mode){(await n(this.hass)).filter((i=>i.url.startsWith(this._lovelaceUrl()))).forEach((i=>{c(this.hass,String(i.id))}))}await h(this.hass,String(this.repository.id)),this._updating=!1,this.active=!1}}]}}),s);export{m as HacsRemovedDialog};
|
||||
@@ -0,0 +1,90 @@
|
||||
import{a as s,H as o,e as t,t as a,$ as i,Z as e,a0 as r,a1 as c,a2 as d,a3 as h,a4 as n,s as l,d as p,r as m,n as u}from"./main-ad130be7.js";import{c as v}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.d262aab0.js";import"./c.3da15c48.js";import"./c.82e03b89.js";import"./c.9b92f489.js";import"./c.82eccc94.js";import"./c.8e28b461.js";import"./c.3f859915.js";import"./c.0ca5587f.js";import"./c.42d6aebd.js";import"./c.710a50fc.js";let g=s([u("hacs-custom-repositories-dialog")],(function(s,o){return{F:class extends o{constructor(...o){super(...o),s(this)}},d:[{kind:"field",decorators:[t()],key:"_error",value:void 0},{kind:"field",decorators:[a()],key:"_progress",value:()=>!1},{kind:"field",decorators:[a()],key:"_addRepositoryData",value:()=>({category:void 0,repository:void 0})},{kind:"field",decorators:[a()],key:"_customRepositories",value:void 0},{kind:"method",key:"shouldUpdate",value:function(s){return s.has("narrow")||s.has("active")||s.has("_error")||s.has("_addRepositoryData")||s.has("_customRepositories")||s.has("_progress")}},{kind:"method",key:"render",value:function(){var s,o;if(!this.active)return i``;const t=[{name:"repository",selector:{text:{}}},{name:"category",selector:{select:{mode:"dropdown",options:this.hacs.info.categories.map((s=>({value:s,label:this.hacs.localize(`common.${s}`)})))}}}];return i`
|
||||
<hacs-dialog
|
||||
.active=${this.active}
|
||||
.hass=${this.hass}
|
||||
.title=${this.hacs.localize("dialog_custom_repositories.title")}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
maxWidth
|
||||
>
|
||||
<div class="content">
|
||||
<div class="list" ?narrow=${this.narrow}>
|
||||
${null!==(s=this._error)&&void 0!==s&&s.message?i`<ha-alert alert-type="error" .rtl=${v(this.hass)}>
|
||||
${this._error.message}
|
||||
</ha-alert>`:""}
|
||||
${null===(o=this._customRepositories)||void 0===o?void 0:o.filter((s=>this.hacs.info.categories.includes(s.category))).map((s=>i`<a
|
||||
href="/hacs/repository/${s.id}"
|
||||
@click=${()=>this.active=!1}
|
||||
>
|
||||
<ha-settings-row>
|
||||
<span slot="heading">${s.name}</span>
|
||||
<span slot="description">${s.full_name} (${s.category})</span>
|
||||
|
||||
<mwc-icon-button
|
||||
@click=${o=>{o.preventDefault(),this._removeRepository(String(s.id))}}
|
||||
>
|
||||
<ha-svg-icon class="delete" .path=${e}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-settings-row>
|
||||
</a>`))}
|
||||
</div>
|
||||
<ha-form
|
||||
?narrow=${this.narrow}
|
||||
.data=${this._addRepositoryData}
|
||||
.schema=${t}
|
||||
.computeLabel=${s=>"category"===s.name?this.hacs.localize("dialog_custom_repositories.category"):this.hacs.localize("common.repository")}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-form>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryaction"
|
||||
raised
|
||||
.disabled=${void 0===this._addRepositoryData.category||void 0===this._addRepositoryData.repository}
|
||||
@click=${this._addRepository}
|
||||
>
|
||||
${this._progress?i`<ha-circular-progress active size="small"></ha-circular-progress>`:this.hacs.localize("common.add")}
|
||||
</mwc-button>
|
||||
</hacs-dialog>
|
||||
`}},{kind:"method",key:"firstUpdated",value:function(){var s;r(this.hass,(s=>this._error=s),c.ERROR),this._customRepositories=null===(s=this.hacs.repositories)||void 0===s?void 0:s.filter((s=>s.custom))}},{kind:"method",key:"_valueChanged",value:function(s){this._addRepositoryData=s.detail.value}},{kind:"method",key:"_addRepository",value:async function(){if(this._error=void 0,this._progress=!0,!this._addRepositoryData.category)return void(this._error={message:this.hacs.localize("dialog_custom_repositories.no_category")});if(!this._addRepositoryData.repository)return void(this._error={message:this.hacs.localize("dialog_custom_repositories.no_repository")});await d(this.hass,this._addRepositoryData.repository,this._addRepositoryData.category);const s=await h(this.hass);this.dispatchEvent(new CustomEvent("update-hacs",{detail:{repositories:s},bubbles:!0,composed:!0})),this._customRepositories=s.filter((s=>s.custom)),this._progress=!1}},{kind:"method",key:"_removeRepository",value:async function(s){this._error=void 0,await n(this.hass,s);const o=await h(this.hass);this.dispatchEvent(new CustomEvent("update-hacs",{detail:{repositories:o},bubbles:!0,composed:!0})),this._customRepositories=o.filter((s=>s.custom))}},{kind:"get",static:!0,key:"styles",value:function(){return[l,p,m`
|
||||
.list {
|
||||
position: relative;
|
||||
max-height: calc(100vh - 500px);
|
||||
overflow: auto;
|
||||
}
|
||||
a {
|
||||
all: unset;
|
||||
}
|
||||
ha-form {
|
||||
display: block;
|
||||
padding: 25px 0;
|
||||
}
|
||||
ha-form[narrow] {
|
||||
background-color: var(--card-background-color);
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
ha-svg-icon {
|
||||
--mdc-icon-size: 36px;
|
||||
}
|
||||
ha-svg-icon:not(.delete) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
ha-settings-row {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
.list[narrow] > ha-settings-row:last-of-type {
|
||||
margin-bottom: 162px;
|
||||
}
|
||||
.delete {
|
||||
color: var(--hcv-color-error);
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.list {
|
||||
max-height: calc(100vh - 162px);
|
||||
}
|
||||
}
|
||||
`]}}]}}),o);export{g as HacsCustomRepositoriesDialog};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,14 @@
|
||||
import{a as e,H as i,e as t,$ as s,n as o}from"./main-ad130be7.js";import"./c.82e03b89.js";import"./c.710a50fc.js";import"./c.8e28b461.js";let c=e([o("hacs-progress-dialog")],(function(e,i){return{F:class extends i{constructor(...i){super(...i),e(this)}},d:[{kind:"field",decorators:[t()],key:"title",value:void 0},{kind:"field",decorators:[t()],key:"content",value:void 0},{kind:"field",decorators:[t()],key:"confirmText",value:void 0},{kind:"field",decorators:[t()],key:"confirm",value:void 0},{kind:"field",decorators:[t({type:Boolean})],key:"_inProgress",value:()=>!1},{kind:"method",key:"shouldUpdate",value:function(e){return e.has("active")||e.has("title")||e.has("content")||e.has("confirmText")||e.has("confirm")||e.has("_inProgress")}},{kind:"method",key:"render",value:function(){return this.active?s`
|
||||
<hacs-dialog .active=${this.active} .hass=${this.hass} title=${this.title||""}>
|
||||
<div class="content">
|
||||
${this.content||""}
|
||||
</div>
|
||||
<mwc-button slot="secondaryaction" ?disabled=${this._inProgress} @click=${this._close}>
|
||||
${this.hacs.localize("common.cancel")}
|
||||
</mwc-button>
|
||||
<mwc-button slot="primaryaction" @click=${this._confirmed}>
|
||||
${this._inProgress?s`<ha-circular-progress active size="small"></ha-circular-progress>`:this.confirmText||this.hacs.localize("common.yes")}</mwc-button
|
||||
>
|
||||
</mwc-button>
|
||||
</hacs-dialog>
|
||||
`:s``}},{kind:"method",key:"_confirmed",value:async function(){this._inProgress=!0,await this.confirm(),this._inProgress=!1,this._close()}},{kind:"method",key:"_close",value:function(){this.active=!1,this.dispatchEvent(new Event("hacs-dialog-closed",{bubbles:!0,composed:!0}))}}]}}),i);export{c as HacsProgressDialog};
|
||||
@@ -0,0 +1 @@
|
||||
Intl.PluralRules&&"function"==typeof Intl.PluralRules.__addLocaleData&&Intl.PluralRules.__addLocaleData({data:{categories:{cardinal:["one","other"],ordinal:["one","two","few","other"]},fn:function(e,l){var a=String(e).split("."),t=!a[1],o=Number(a[0])==e,n=o&&a[0].slice(-1),r=o&&a[0].slice(-2);return l?1==n&&11!=r?"one":2==n&&12!=r?"two":3==n&&13!=r?"few":"other":1==e&&t?"one":"other"}},locale:"en"});
|
||||
@@ -0,0 +1,7 @@
|
||||
import{a as r,h as e,e as t,t as s,$ as i,ah as o,n as a}from"./main-ad130be7.js";import{e as n,c}from"./c.50bfd408.js";const d={hacs_repository:{redirect:"/hacs/repository",params:{owner:"string",repository:"string",category:"string?"}}};r([a("hacs-my-redirect")],(function(r,e){return{F:class extends e{constructor(...e){super(...e),r(this)}},d:[{kind:"field",decorators:[t({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[t({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[t({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[s()],key:"_error",value:void 0},{kind:"method",key:"firstUpdated",value:function(r){const e=this.route.path.indexOf("/",1),t=this.route.path.substr(e+1),s=d[t];if(!s)return void(this._error=this.hacs.localize("my.not_supported",{link:i`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href="https://my.home-assistant.io/faq.html#supported-pages"
|
||||
>
|
||||
${this.hacs.localize("my.faq_link")}
|
||||
</a>`}));let a;try{a=this._createRedirectUrl(s)}catch(r){return void(this._error=this.hacs.localize("my.error"))}o(a,{replace:!0})}},{kind:"method",key:"render",value:function(){return this._error?i`<hass-error-screen .error=${this._error}></hass-error-screen>`:i``}},{kind:"method",key:"_createRedirectUrl",value:function(r){const e=this._createRedirectParams(r);return`${r.redirect}${e}`}},{kind:"method",key:"_createRedirectParams",value:function(r){const e=n();if(!r.params&&!Object.keys(e).length)return"";const t={};for(const[s,i]of Object.entries(r.params||{}))if(e[s]||!i.endsWith("?")){if(!e[s]||!this._checkParamType(i,e[s]))throw Error();t[s]=e[s]}return`?${c(t)}`}},{kind:"method",key:"_checkParamType",value:function(r,e){return"string"===r||"string?"===r}}]}}),e);export{d as REDIRECTS};
|
||||
@@ -0,0 +1,436 @@
|
||||
import{aw as e,ax as t,ay as i,a6 as a,a7 as o,a8 as s,a as n,h as r,e as l,$ as d,o as c,r as h,n as p,m as g,az as u,ah as m,aA as v,c as f,aB as y,aC as b,aD as w,d as k}from"./main-ad130be7.js";import"./c.82eccc94.js";import{A as x}from"./c.bc5a73e9.js";import{i as $}from"./c.21c042d4.js";import{c as _}from"./c.4a97632a.js";import"./c.f1291e50.js";import"./c.2d5ed670.js";import"./c.11ad1623.js";import{b as z}from"./c.0a1cf8d0.js";import{s as C}from"./c.2645c235.js";import"./c.8e28b461.js";import"./c.f6611997.js";import"./c.743a15a1.js";import"./c.5d3ce9d6.js";import"./c.4266acdb.js";customElements.define("ha-icon-next",class extends e{connectedCallback(){super.connectedCallback(),setTimeout((()=>{this.path="ltr"===window.getComputedStyle(this).direction?t:i}),100)}}),a({_template:o`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
/**
|
||||
* Force app-header-layout to have its own stacking context so that its parent can
|
||||
* control the stacking of it relative to other elements (e.g. app-drawer-layout).
|
||||
* This could be done using \`isolation: isolate\`, but that's not well supported
|
||||
* across browsers.
|
||||
*/
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#wrapper ::slotted([slot=header]) {
|
||||
@apply --layout-fixed-top;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#wrapper.initializing ::slotted([slot=header]) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper ::slotted([slot=header]) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper.initializing ::slotted([slot=header]) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper #contentContainer {
|
||||
@apply --layout-fit;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper.initializing #contentContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([fullbleed]) {
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-fit;
|
||||
}
|
||||
|
||||
:host([fullbleed]) #wrapper,
|
||||
:host([fullbleed]) #wrapper #contentContainer {
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-flex;
|
||||
}
|
||||
|
||||
#contentContainer {
|
||||
/* Create a stacking context here so that all children appear below the header. */
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
:host([has-scrolling-region]) #wrapper #contentContainer {
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div id="wrapper" class="initializing">
|
||||
<slot id="headerSlot" name="header"></slot>
|
||||
|
||||
<div id="contentContainer">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`,is:"app-header-layout",behaviors:[x],properties:{hasScrollingRegion:{type:Boolean,value:!1,reflectToAttribute:!0}},observers:["resetLayout(isAttached, hasScrollingRegion)"],get header(){return s(this.$.headerSlot).getDistributedNodes()[0]},_updateLayoutStates:function(){var e=this.header;if(this.isAttached&&e){this.$.wrapper.classList.remove("initializing"),e.scrollTarget=this.hasScrollingRegion?this.$.contentContainer:this.ownerDocument.documentElement;var t=e.offsetHeight;this.hasScrollingRegion?(e.style.left="",e.style.right=""):requestAnimationFrame(function(){var t=this.getBoundingClientRect(),i=document.documentElement.clientWidth-t.right;e.style.left=t.left+"px",e.style.right=i+"px"}.bind(this));var i=this.$.contentContainer.style;e.fixed&&!e.condenses&&this.hasScrollingRegion?(i.marginTop=t+"px",i.paddingTop=""):(i.paddingTop=t+"px",i.marginTop="")}}});class E extends(customElements.get("app-header-layout")){static get template(){return o`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
/**
|
||||
* Force app-header-layout to have its own stacking context so that its parent can
|
||||
* control the stacking of it relative to other elements (e.g. app-drawer-layout).
|
||||
* This could be done using \`isolation: isolate\`, but that's not well supported
|
||||
* across browsers.
|
||||
*/
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#wrapper ::slotted([slot="header"]) {
|
||||
@apply --layout-fixed-top;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#wrapper.initializing ::slotted([slot="header"]) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper ::slotted([slot="header"]) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region])
|
||||
#wrapper.initializing
|
||||
::slotted([slot="header"]) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper #contentContainer {
|
||||
@apply --layout-fit;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:host([has-scrolling-region]) #wrapper.initializing #contentContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#contentContainer {
|
||||
/* Create a stacking context here so that all children appear below the header. */
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
/* Using 'transform' will cause 'position: fixed' elements to behave like
|
||||
'position: absolute' relative to this element. */
|
||||
transform: translate(0);
|
||||
margin-left: env(safe-area-inset-left);
|
||||
margin-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
@media print {
|
||||
:host([has-scrolling-region]) #wrapper #contentContainer {
|
||||
overflow-y: visible;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="wrapper" class="initializing">
|
||||
<slot id="headerSlot" name="header"></slot>
|
||||
|
||||
<div id="contentContainer"><slot></slot></div>
|
||||
<slot id="fab" name="fab"></slot>
|
||||
</div>
|
||||
`}}customElements.define("ha-app-layout",E),n([p("ha-config-section")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[l()],key:"isWide",value:()=>!1},{kind:"field",decorators:[l({type:Boolean})],key:"vertical",value:()=>!1},{kind:"field",decorators:[l({type:Boolean,attribute:"full-width"})],key:"fullWidth",value:()=>!1},{kind:"method",key:"render",value:function(){return d`
|
||||
<div
|
||||
class="content ${c({narrow:!this.isWide,"full-width":this.fullWidth})}"
|
||||
>
|
||||
<div class="header"><slot name="header"></slot></div>
|
||||
<div
|
||||
class="together layout ${c({narrow:!this.isWide,vertical:this.vertical||!this.isWide,horizontal:!this.vertical&&this.isWide})}"
|
||||
>
|
||||
<div class="intro"><slot name="introduction"></slot></div>
|
||||
<div class="panel flex-auto"><slot></slot></div>
|
||||
</div>
|
||||
</div>
|
||||
`}},{kind:"get",static:!0,key:"styles",value:function(){return h`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.content {
|
||||
padding: 28px 20px 0;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-headline_-_-webkit-font-smoothing
|
||||
);
|
||||
font-size: var(--paper-font-headline_-_font-size);
|
||||
font-weight: var(--paper-font-headline_-_font-weight);
|
||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
||||
line-height: var(--paper-font-headline_-_line-height);
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.together {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
font-family: var(--paper-font-subhead_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-subhead_-_-webkit-font-smoothing
|
||||
);
|
||||
font-weight: var(--paper-font-subhead_-_font-weight);
|
||||
line-height: var(--paper-font-subhead_-_line-height);
|
||||
width: 100%;
|
||||
opacity: var(--dark-primary-opacity);
|
||||
font-size: 14px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.horizontal .intro {
|
||||
max-width: 400px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin-top: -24px;
|
||||
}
|
||||
|
||||
.panel ::slotted(*) {
|
||||
margin-top: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.narrow.content {
|
||||
max-width: 640px;
|
||||
}
|
||||
.narrow .together {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.narrow .intro {
|
||||
padding-bottom: 20px;
|
||||
margin-right: 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.full-width .layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
`}}]}}),r);const j=g(((e,t)=>{var i,a;const o=[],s=[],n=[];var r,l;return e.repositories.forEach((t=>{var i;if("pending-restart"===t.status&&n.push(t),e.addedToLovelace(e,t)||s.push(t),t.installed&&null!==(i=e.removed.map((e=>e.repository)))&&void 0!==i&&i.includes(t.full_name)){const i=e.removed.find((e=>e.repository===t.full_name));o.push({name:e.localize("entry.messages.removed_repository",{repository:i.repository}),info:i.reason,severity:"warning",dialog:"remove",repository:t})}})),null!==(i=e.info)&&void 0!==i&&i.startup&&["setup","waiting","startup"].includes(e.info.stage)&&o.push({name:e.localize(`entry.messages.${e.info.stage}.title`),info:e.localize(`entry.messages.${e.info.stage}.content`),severity:"warning"}),null!==(a=e.info)&&void 0!==a&&a.disabled_reason?[{name:e.localize("entry.messages.disabled.title"),secondary:e.localize(`entry.messages.disabled.${null===(r=e.info)||void 0===r?void 0:r.disabled_reason}.title`),info:e.localize(`entry.messages.disabled.${null===(l=e.info)||void 0===l?void 0:l.disabled_reason}.description`),severity:"error"}]:(s.length>0&&o.push({name:e.localize("entry.messages.resources.title"),info:e.localize("entry.messages.resources.content",{number:s.length}),severity:"error"}),n.length>0&&o.push({name:e.localize("entry.messages.restart.title"),path:t?"/_my_redirect/server_controls":void 0,info:e.localize("entry.messages.restart.content",{number:n.length,pluralWording:1===n.length?e.localize("common.integration"):e.localize("common.integration_plural")}),severity:"error"}),o)}));let S=n([p("hacs-entry-panel")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[l({attribute:!1})],key:"hacs",value:void 0},{kind:"field",decorators:[l({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[l({attribute:!1})],key:"route",value:void 0},{kind:"field",decorators:[l({type:Boolean,reflect:!0})],key:"narrow",value:void 0},{kind:"field",decorators:[l({type:Boolean})],key:"isWide",value:void 0},{kind:"method",key:"render",value:function(){var e,t;const i=[],a=[],o=j(this.hacs,$(this.hass,"my"));return this.hacs.repositories.forEach((e=>{e.pending_upgrade&&i.push(e)})),o.forEach((e=>{a.push({iconPath:u,name:e.name,info:e.info,secondary:e.secondary,path:e.path||"",severity:e.severity,dialog:e.dialog,repository:e.repository})})),this.dispatchEvent(new CustomEvent("update-hacs",{detail:{messages:a,updates:i},bubbles:!0,composed:!0})),d`
|
||||
<ha-app-layout>
|
||||
<app-header fixed slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button .hass=${this.hass} .narrow=${this.narrow}></ha-menu-button>
|
||||
<div main-title>${this.narrow?"HACS":"Home Assistant Community Store"}</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide} full-width>
|
||||
${0!==(null===(e=this.hacs.messages)||void 0===e?void 0:e.length)?this.hacs.messages.map((e=>d`
|
||||
<ha-alert
|
||||
.alertType=${e.severity}
|
||||
.title=${e.secondary?`${e.name} - ${e.secondary}`:e.name}
|
||||
.rtl=${_(this.hass)}
|
||||
>
|
||||
${e.info}
|
||||
<mwc-button
|
||||
slot="action"
|
||||
.label=${e.path?this.hacs.localize("common.navigate"):e.dialog?this.hacs.localize("common.show"):""}
|
||||
@click=${()=>e.path?m(e.path):this._openDialog(e)}
|
||||
>
|
||||
</mwc-button>
|
||||
</ha-alert>
|
||||
`)):(this.narrow,"")}
|
||||
${0!==(null===(t=this.hacs.updates)||void 0===t?void 0:t.length)?d` <ha-card outlined>
|
||||
<div class="title">${this.hacs.localize("common.updates")}</div>
|
||||
<mwc-list>
|
||||
${v(this.hacs.updates).map((e=>d`
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
disableHref
|
||||
twoline
|
||||
@click=${()=>this._openUpdateDialog(e)}
|
||||
>
|
||||
${"integration"===e.category?d`
|
||||
<img
|
||||
loading="lazy"
|
||||
.src=${z({domain:e.domain,darkOptimized:this.hass.themes.darkMode,type:"icon"})}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
@load=${this._onImageLoad}
|
||||
slot="graphic"
|
||||
/>
|
||||
`:d`
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
path="${f}"
|
||||
style="padding-left: 0; height: 40px; width: 40px;"
|
||||
>
|
||||
</ha-svg-icon>
|
||||
`}
|
||||
<span>${e.name}</span>
|
||||
<span slot="secondary"
|
||||
>${this.hacs.localize("sections.pending_repository_upgrade",{downloaded:e.installed_version,available:e.available_version})}</span
|
||||
>
|
||||
</ha-clickable-list-item>
|
||||
`))}
|
||||
</mwc-list>
|
||||
</ha-card>`:""}
|
||||
|
||||
<ha-card outlined>
|
||||
<mwc-list>
|
||||
${this.hacs.sections.map((e=>d`
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
twoline
|
||||
.hasMeta=${!this.narrow}
|
||||
href=${e.path}
|
||||
>
|
||||
<div
|
||||
slot="graphic"
|
||||
class=${e.iconColor?"icon-background":""}
|
||||
.style="background-color: ${e.iconColor||"undefined"}"
|
||||
>
|
||||
<ha-svg-icon .path=${e.iconPath}></ha-svg-icon>
|
||||
</div>
|
||||
<span>${e.name}</span>
|
||||
<span slot="secondary">${e.description}</span>
|
||||
${this.narrow?"":d`<ha-icon-next slot="meta"></ha-icon-next>`}
|
||||
</ha-clickable-list-item>
|
||||
`))}
|
||||
${$(this.hass,"my")&&$(this.hass,"hassio")?d`
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
disableHref
|
||||
twoline
|
||||
@click=${this._openSupervisorDialog}
|
||||
.hasMeta=${!this.narrow}
|
||||
>
|
||||
<div
|
||||
class="icon-background"
|
||||
slot="graphic"
|
||||
style="background-color: rgb(64, 132, 205)"
|
||||
>
|
||||
<ha-svg-icon .path=${y}></ha-svg-icon>
|
||||
</div>
|
||||
<span>${this.hacs.localize("sections.addon.title")}</span>
|
||||
<span slot="secondary"
|
||||
>${this.hacs.localize("sections.addon.description")}</span
|
||||
>
|
||||
</ha-clickable-list-item>
|
||||
`:""}
|
||||
<ha-clickable-list-item
|
||||
graphic="avatar"
|
||||
twoline
|
||||
@click=${this._openAboutDialog}
|
||||
disableHref
|
||||
>
|
||||
<div
|
||||
class="icon-background"
|
||||
slot="graphic"
|
||||
style="background-color: rgb(74, 89, 99)"
|
||||
>
|
||||
<ha-svg-icon .path=${b}></ha-svg-icon>
|
||||
</div>
|
||||
<span>${this.hacs.localize("sections.about.title")}</span>
|
||||
<span slot="secondary">${this.hacs.localize("sections.about.description")}</span>
|
||||
</ha-clickable-list-item>
|
||||
</mwc-list>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</ha-app-layout>
|
||||
`}},{kind:"method",key:"_onImageLoad",value:function(e){e.target.style.visibility="initial"}},{kind:"method",key:"_onImageError",value:function(e){e.target&&(e.target.outerHTML=`\n <div slot="item-icon" class="icon-background">\n <ha-svg-icon path="${f}" style="padding-left: 0; height: 40px; width: 40px;"></ha-svg-icon>\n </div>`)}},{kind:"method",key:"_openDialog",value:function(e){e.dialog&&("remove"==e.dialog&&(e.dialog="removed"),this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:e.dialog,repository:e.repository},bubbles:!0,composed:!0})))}},{kind:"method",key:"_openUpdateDialog",value:function(e){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"update",repository:e.id},bubbles:!0,composed:!0}))}},{kind:"method",key:"_openAboutDialog",value:async function(){C(this,this.hacs)}},{kind:"method",key:"_openSupervisorDialog",value:async function(){this.dispatchEvent(new CustomEvent("hacs-dialog",{detail:{type:"navigate",path:"/_my_redirect/supervisor"},bubbles:!0,composed:!0}))}},{kind:"get",static:!0,key:"styles",value:function(){return[w,k,h`
|
||||
:host {
|
||||
--mdc-list-vertical-padding: 0;
|
||||
}
|
||||
ha-card:last-child {
|
||||
margin-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
:host(:not([narrow])) ha-card:last-child {
|
||||
margin-bottom: max(24px, env(safe-area-inset-bottom));
|
||||
}
|
||||
ha-config-section {
|
||||
margin: auto;
|
||||
margin-top: -32px;
|
||||
max-width: 600px;
|
||||
}
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
a.button {
|
||||
display: block;
|
||||
color: var(--primary-color);
|
||||
padding: 16px;
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
padding: 16px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 600px) {
|
||||
ha-card {
|
||||
border-width: 1px 0;
|
||||
border-radius: 0;
|
||||
box-shadow: unset;
|
||||
}
|
||||
ha-config-section {
|
||||
margin-top: -42px;
|
||||
}
|
||||
}
|
||||
|
||||
ha-svg-icon,
|
||||
ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: block;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding: 8px;
|
||||
}
|
||||
.icon-background {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.icon-background ha-svg-icon {
|
||||
color: #fff;
|
||||
}
|
||||
ha-clickable-list-item {
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 0;
|
||||
}
|
||||
`]}}]}}),r);export{S as HacsEntryPanel};
|
||||
@@ -0,0 +1,74 @@
|
||||
import{a as i,h as a,e as t,t as o,i as s,$ as e,D as r,j as n,A as l,r as d,n as c}from"./main-ad130be7.js";import"./c.710a50fc.js";import"./c.8d4c35ad.js";import"./c.8e28b461.js";i([c("dialog-box")],(function(i,a){return{F:class extends a{constructor(...a){super(...a),i(this)}},d:[{kind:"field",decorators:[t({attribute:!1})],key:"hass",value:void 0},{kind:"field",decorators:[o()],key:"_params",value:void 0},{kind:"field",decorators:[s("ha-textfield")],key:"_textField",value:void 0},{kind:"method",key:"showDialog",value:async function(i){this._params=i}},{kind:"method",key:"closeDialog",value:function(){var i,a;return!(null!==(i=this._params)&&void 0!==i&&i.confirmation||null!==(a=this._params)&&void 0!==a&&a.prompt)&&(!this._params||(this._dismiss(),!0))}},{kind:"method",key:"render",value:function(){if(!this._params)return e``;const i=this._params.confirmation||this._params.prompt;return e`
|
||||
<ha-dialog
|
||||
open
|
||||
?scrimClickAction=${i}
|
||||
?escapeKeyAction=${i}
|
||||
@closed=${this._dialogClosed}
|
||||
defaultAction="ignore"
|
||||
.heading=${e`${this._params.warning?e`<ha-svg-icon
|
||||
.path=${r}
|
||||
style="color: var(--warning-color)"
|
||||
></ha-svg-icon> `:""}${this._params.title?this._params.title:this._params.confirmation&&this.hass.localize("ui.dialogs.generic.default_confirmation_title")}`}
|
||||
>
|
||||
<div>
|
||||
${this._params.text?e`
|
||||
<p class=${this._params.prompt?"no-bottom-padding":""}>
|
||||
${this._params.text}
|
||||
</p>
|
||||
`:""}
|
||||
${this._params.prompt?e`
|
||||
<ha-textfield
|
||||
dialogInitialFocus
|
||||
value=${n(this._params.defaultValue)}
|
||||
.placeholder=${n(this._params.placeholder)}
|
||||
.label=${this._params.inputLabel?this._params.inputLabel:""}
|
||||
.type=${this._params.inputType?this._params.inputType:"text"}
|
||||
></ha-textfield>
|
||||
`:""}
|
||||
</div>
|
||||
${i&&e`
|
||||
<mwc-button @click=${this._dismiss} slot="secondaryAction">
|
||||
${this._params.dismissText?this._params.dismissText:this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button
|
||||
@click=${this._confirm}
|
||||
?dialogInitialFocus=${!this._params.prompt}
|
||||
slot="primaryAction"
|
||||
>
|
||||
${this._params.confirmText?this._params.confirmText:this.hass.localize("ui.dialogs.generic.ok")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`}},{kind:"method",key:"_dismiss",value:function(){var i;null!==(i=this._params)&&void 0!==i&&i.cancel&&this._params.cancel(),this._close()}},{kind:"method",key:"_confirm",value:function(){var i;this._params.confirm&&this._params.confirm(null===(i=this._textField)||void 0===i?void 0:i.value);this._close()}},{kind:"method",key:"_dialogClosed",value:function(i){"ignore"!==i.detail.action&&this._dismiss()}},{kind:"method",key:"_close",value:function(){this._params&&(this._params=void 0,l(this,"dialog-closed",{dialog:this.localName}))}},{kind:"get",static:!0,key:"styles",value:function(){return d`
|
||||
:host([inert]) {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.no-bottom-padding {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-dialog {
|
||||
--mdc-dialog-heading-ink-color: var(--primary-text-color);
|
||||
--mdc-dialog-content-ink-color: var(--primary-text-color);
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
}
|
||||
@media all and (min-width: 600px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 400px;
|
||||
}
|
||||
}
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
`}}]}}),a);
|
||||
@@ -0,0 +1,114 @@
|
||||
import{a as e,h as t,e as i,$ as o,o as r,z as s,A as n,r as a,n as c,C as l,D as d,E as u,F as p}from"./main-ad130be7.js";const y={info:l,warning:d,error:u,success:p};e([c("ha-alert")],(function(e,t){return{F:class extends t{constructor(...t){super(...t),e(this)}},d:[{kind:"field",decorators:[i()],key:"title",value:()=>""},{kind:"field",decorators:[i({attribute:"alert-type"})],key:"alertType",value:()=>"info"},{kind:"field",decorators:[i({type:Boolean})],key:"dismissable",value:()=>!1},{kind:"field",decorators:[i({type:Boolean})],key:"rtl",value:()=>!1},{kind:"method",key:"render",value:function(){return o`
|
||||
<div
|
||||
class="issue-type ${r({rtl:this.rtl,[this.alertType]:!0})}"
|
||||
role="alert"
|
||||
>
|
||||
<div class="icon ${this.title?"":"no-title"}">
|
||||
<slot name="icon">
|
||||
<ha-svg-icon .path=${y[this.alertType]}></ha-svg-icon>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="main-content">
|
||||
${this.title?o`<div class="title">${this.title}</div>`:""}
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="action">
|
||||
<slot name="action">
|
||||
${this.dismissable?o`<ha-icon-button
|
||||
@click=${this._dismiss_clicked}
|
||||
label="Dismiss alert"
|
||||
.path=${s}
|
||||
></ha-icon-button>`:""}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}},{kind:"method",key:"_dismiss_clicked",value:function(){n(this,"alert-dismissed-clicked")}},{kind:"field",static:!0,key:"styles",value:()=>a`
|
||||
.issue-type {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
}
|
||||
.issue-type.rtl {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.issue-type::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
border-radius: 4px;
|
||||
}
|
||||
.icon {
|
||||
z-index: 1;
|
||||
}
|
||||
.icon.no-title {
|
||||
align-self: center;
|
||||
}
|
||||
.issue-type.rtl > .content {
|
||||
flex-direction: row-reverse;
|
||||
text-align: right;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.action {
|
||||
z-index: 1;
|
||||
width: min-content;
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
}
|
||||
.main-content {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
margin-left: 8px;
|
||||
margin-right: 0;
|
||||
}
|
||||
.issue-type.rtl > .content > .main-content {
|
||||
margin-left: 0;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.title {
|
||||
margin-top: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.action mwc-button,
|
||||
.action ha-icon-button {
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
--mdc-icon-button-size: 36px;
|
||||
}
|
||||
.issue-type.info > .icon {
|
||||
color: var(--info-color);
|
||||
}
|
||||
.issue-type.info::after {
|
||||
background-color: var(--info-color);
|
||||
}
|
||||
|
||||
.issue-type.warning > .icon {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
.issue-type.warning::after {
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
.issue-type.error > .icon {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.issue-type.error::after {
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
|
||||
.issue-type.success > .icon {
|
||||
color: var(--success-color);
|
||||
}
|
||||
.issue-type.success::after {
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
`}]}}),t);
|
||||
@@ -0,0 +1 @@
|
||||
const t=(t,e)=>{const n={};return e&&(e.type&&(n.type_filter=e.type),e.domain&&(n.domain=e.domain)),t.callWS({type:"config_entries/get",...n})};export{t as g};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
const s=async(s,o)=>s.connection.sendMessagePromise({type:"hacs/repository/info",repository_id:o}),o=async(s,o,e)=>s.connection.sendMessagePromise({type:"hacs/repository/download",repository:o,version:e}),e=async(s,o,e)=>s.connection.sendMessagePromise({type:"hacs/repository/version",repository:o,version:e});export{o as a,s as f,e as r};
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
try {
|
||||
new Function("import('/hacsfiles/frontend/main-ad130be7.js')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '/hacsfiles/frontend/main-ad130be7.js';
|
||||
el.type = 'module';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"./src/main.ts": "main-ad130be7.js"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
VERSION="20220906112053"
|
||||
@@ -0,0 +1,5 @@
|
||||
"""HACS Frontend"""
|
||||
from .version import VERSION
|
||||
|
||||
def locate_dir():
|
||||
return __path__[0]
|
||||
@@ -0,0 +1 @@
|
||||
!function(){function n(n){var e=document.createElement("script");e.src=n,document.body.appendChild(e)}if(/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent))n("/hacsfiles/frontend/frontend_es5/entrypoint.yqQWLcDGcBc.js");else try{new Function("import('/hacsfiles/frontend/frontend_latest/entrypoint.4szXpxNxoP4.js')")()}catch(e){n("/hacsfiles/frontend/frontend_es5/entrypoint.yqQWLcDGcBc.js")}}()
|
||||
@@ -0,0 +1 @@
|
||||
!function(){function e(e){var n=document.createElement("script");n.src=e,document.body.appendChild(n)}if(/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent))e("/hacsfiles/frontend/frontend_es5/extra.Gq4ioglXLxE.js");else try{new Function("import('/hacsfiles/frontend/frontend_latest/extra.1KC8NSjONmE.js')")()}catch(n){e("/hacsfiles/frontend/frontend_es5/extra.Gq4ioglXLxE.js")}}()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user