import HAKit public extension HACachesContainer { var panels: HACache { self[HAPanelsCacheKey.self] } } public struct HAPanel: HADataDecodable, Codable, Equatable { public var icon: String? public var title: String public var path: String public var component: String public var showInSidebar: Bool public init(data: HAData) throws { let component: String = try data.decode("component_name") self.component = component let fallbackIcon: String? = { () -> String? in switch component { case "profile": return "mdi:account" case "lovelace": return "mdi:view-dashboard" default: return nil } }() self.showInSidebar = data.decode("show_in_sidebar", fallback: true) self.icon = data.decode("icon", fallback: fallbackIcon) self.path = try data.decode("url_path") let title: String = data.decode("title", fallback: component) let possibleFrontendKey: String if path == "lovelace" { possibleFrontendKey = "panel::states" } else { possibleFrontendKey = "panel::\(title)" } self.title = Current.localized.frontend(possibleFrontendKey) ?? title } public init(icon: String?, title: String, path: String, component: String, showInSidebar: Bool) { self.icon = icon self.title = title self.path = path self.component = component self.showInSidebar = showInSidebar } } public struct HAPanels: HADataDecodable, Codable, Equatable { public var panelsByPath: [String: HAPanel] public var allPanels: [HAPanel] public init(panelsByPath: [String: HAPanel]) { self.panelsByPath = panelsByPath self.allPanels = panelsByPath.values.sorted(by: { let sortedByTitle = $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending switch ($0.component, $1.component) { case ("lovelace", "lovelace"): return sortedByTitle case ("lovelace", _): return true case (_, "lovelace"): return false default: // from the frontend as of 9a928259549b255ae79fbdb412538109e31d62d2 2021-07-28 // https://github.com/home-assistant/frontend/blob/b26c44b2/src/components/ha-sidebar.ts#L55-L63 let pathSortValue = [ "energy": 1, "map": 2, "logbook": 3, "history": 4, "developer-tools": 9, "hassio": 10, "config": 11, ] let sort0 = pathSortValue[$0.path, default: -1] let sort1 = pathSortValue[$1.path, default: -1] if sort0 == sort1 { return sortedByTitle } else { return sort0 < sort1 } } }) } public init(data: HAData) throws { guard case let .dictionary(dictionary) = data else { throw HADataError.missingKey("root") } try self.init( panelsByPath: dictionary .compactMapKeys { if $0.hasPrefix("_") { return nil } else { return $0 } } .mapValues { try HAPanel(data: .init(value: $0)) } // non-show_in_sidebar dashboards have badly-named titles .filter(\.value.showInSidebar) ) } } public extension HATypedRequest { static func panels() -> HATypedRequest { .init(request: .init(type: "get_panels")) } } private struct HAPanelsCacheKey: HACacheKey { static func create(connection: HAConnection, data: [String: Any]) -> HACache { HACache( connection: connection, populate: .init( request: .panels(), transform: { $0.incoming } ), subscribe: [ HACacheSubscribeInfo( subscription: .events("panels_updated"), transform: { _ in .reissuePopulate } ), ] ) } }