import QtQuick import Quickshell import qs.Common import qs.Services import qs.Widgets Item { id: haSettings property var settings: null readonly property var daemon: (typeof PluginService !== "undefined" && PluginService.pluginInstances) ? PluginService.pluginInstances["gameBar"] : null readonly property var service: daemon ? daemon.homeService : null readonly property var entityList: service ? service.entityList : [] property string filterText: "" // Read straight from the daemon's (reactive) plugin data. Caching this in a // local list loses the saved picks, because PluginSettings.loadValue() still // returns the default while pluginService is being injected - and the next // pick would then save that empty list over the stored one. readonly property var selectedIds: { const data = service ? service.pluginData : null; const stored = data ? data.haEntities : null; if (Array.isArray(stored)) return stored; const fallback = settings ? settings.loadValue("haEntities", []) : []; return Array.isArray(fallback) ? fallback : []; } readonly property var filteredEntities: { const query = filterText.trim().toLowerCase(); const list = haSettings.entityList; if (query === "") return list; return list.filter(entity => { const name = (entity.attributes && entity.attributes.friendly_name) ? entity.attributes.friendly_name : ""; return entity.entity_id.toLowerCase().includes(query) || name.toLowerCase().includes(query); }); } readonly property string statusText: { if (!service) return "Plugin daemon not running"; if (!service.configured) return "Enter a URL and token, then refresh"; if (service.loading) return "Contacting Home Assistant…"; if (service.lastError !== "") return service.lastError; if (service.everLoaded) return entityList.length + " entities · " + selectedIds.length + " picked"; return "Not loaded yet"; } function isSelected(entityId) { return haSettings.selectedIds.indexOf(entityId) !== -1; } function setSelected(entityId, selected) { const next = haSettings.selectedIds.slice(); const index = next.indexOf(entityId); if (selected && index === -1) next.push(entityId); else if (!selected && index !== -1) next.splice(index, 1); if (settings) settings.saveValue("haEntities", next); } implicitHeight: column.implicitHeight height: implicitHeight Component.onCompleted: { if (service && service.configured && !service.everLoaded) service.refresh(); } Column { id: column anchors.left: parent.left anchors.right: parent.right spacing: Theme.spacingS StyledText { text: "Home Assistant" font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium color: Theme.surfaceText } StyledText { width: parent.width text: "Pick the entities you want in the Home widget. The access token is stored in plugin_settings.json." font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText wrapMode: Text.WordWrap } Row { width: parent.width spacing: Theme.spacingS StyledText { anchors.verticalCenter: parent.verticalCenter width: Math.round(parent.width * 0.3) text: "Status" font.pixelSize: Theme.fontSizeSmall color: Theme.surfaceVariantText } StyledText { anchors.verticalCenter: parent.verticalCenter width: parent.width - Math.round(parent.width * 0.3) - refreshButton.width - parent.spacing * 2 text: haSettings.statusText font.pixelSize: Theme.fontSizeSmall color: (haSettings.service && haSettings.service.lastError !== "") ? Theme.error : Theme.surfaceText elide: Text.ElideRight } DankButton { id: refreshButton text: "Refresh" iconName: "refresh" enabled: haSettings.service !== null && haSettings.service.configured onClicked: haSettings.service.refresh() } } DankTextField { width: parent.width placeholderText: "Filter entities (e.g. light, kitchen)" leftIconName: "search" showClearButton: true text: haSettings.filterText onTextChanged: haSettings.filterText = text visible: haSettings.entityList.length > 0 } Rectangle { width: parent.width height: 280 radius: Theme.cornerRadius color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) border.color: Theme.outlineMedium border.width: Theme.layerOutlineWidth visible: haSettings.entityList.length > 0 DankListView { id: entityListView anchors.fill: parent anchors.margins: Theme.spacingS clip: true spacing: 2 model: haSettings.filteredEntities delegate: Rectangle { id: entityRow required property var modelData readonly property bool picked: haSettings.isSelected(modelData.entity_id) width: entityListView.width height: 44 radius: Theme.cornerRadius color: picked ? Theme.primarySelected : (rowArea.containsMouse ? Theme.surfaceHover : "transparent") Column { anchors.left: parent.left anchors.leftMargin: Theme.spacingS anchors.right: pickToggle.left anchors.rightMargin: Theme.spacingS anchors.verticalCenter: parent.verticalCenter spacing: 0 StyledText { width: parent.width text: (entityRow.modelData.attributes && entityRow.modelData.attributes.friendly_name) ? entityRow.modelData.attributes.friendly_name : entityRow.modelData.entity_id font.pixelSize: Theme.fontSizeSmall font.weight: entityRow.picked ? Font.Medium : Font.Normal color: Theme.surfaceText elide: Text.ElideRight } StyledText { width: parent.width text: entityRow.modelData.entity_id + " · " + entityRow.modelData.state font.pixelSize: Theme.fontSizeSmall - 1 color: Theme.surfaceVariantText elide: Text.ElideRight } } DankToggle { id: pickToggle anchors.right: parent.right anchors.rightMargin: Theme.spacingS anchors.verticalCenter: parent.verticalCenter hideText: true checked: entityRow.picked onToggled: isChecked => haSettings.setSelected(entityRow.modelData.entity_id, isChecked) } MouseArea { id: rowArea anchors.fill: parent anchors.rightMargin: pickToggle.width + Theme.spacingM hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: haSettings.setSelected(entityRow.modelData.entity_id, !entityRow.picked) } } } } } }