Skip to content

Commit 0450587

Browse files
committed
Initial Commit
1 parent 96ac1bf commit 0450587

8 files changed

Lines changed: 1335 additions & 0 deletions

File tree

hassio/BarWidget.qml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import QtQuick
2+
import QtQuick.Layouts
3+
import Quickshell
4+
import qs.Commons
5+
import qs.Widgets
6+
7+
NIconButton {
8+
id: root
9+
10+
property var pluginApi: null
11+
property ShellScreen screen
12+
property string widgetId: ""
13+
property string section: ""
14+
property int sectionWidgetIndex: -1
15+
property int sectionWidgetsCount: 0
16+
17+
property var main: pluginApi?.mainInstance ?? null
18+
19+
readonly property string _status: {
20+
if (!!(root.main && !root.main.connected && !root.main.authFailed && root.main.haToken !== ""))
21+
return "Disconnected";
22+
if (!!(root.main && (root.main.authFailed || root.main.haToken === "")))
23+
return "Connecting";
24+
return "Connected";
25+
}
26+
27+
icon: "smart-home"
28+
colorFg: {
29+
switch (root._status) {
30+
case "Connected":
31+
return Color.mPrimary;
32+
case "Disconnected":
33+
return Color.mError;
34+
case "Connecting":
35+
return Color.mOnError;
36+
default:
37+
return Color.mError;
38+
}
39+
}
40+
41+
colorBg: Color.mSurfaceVariant
42+
colorBgHover: Color.mHover
43+
colorFgHover: Color.mOnHover
44+
colorBorder: "transparent"
45+
colorBorderHover: "transparent"
46+
47+
onClicked: pluginApi.togglePanel(root.screen, this)
48+
49+
tooltipText: "Status: " + (root._status)
50+
51+
implicitHeight: Style.barHeight
52+
53+
// Pulse animation while connecting
54+
SequentialAnimation on opacity {
55+
running: root._status === "Connecting"
56+
loops: Animation.Infinite
57+
NumberAnimation {
58+
to: 0.3
59+
duration: 600
60+
easing.type: Easing.InOutSine
61+
}
62+
NumberAnimation {
63+
to: 1.0
64+
duration: 600
65+
easing.type: Easing.InOutSine
66+
}
67+
}
68+
69+
// Snap back to full opacity when done
70+
opacity: root._status !== "Connecting" ? 1.0 : opacity
71+
}

hassio/BrowserView.qml

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import QtQuick
2+
import QtQuick.Layouts
3+
import qs.Commons
4+
import qs.Widgets
5+
6+
Item {
7+
id: root
8+
property var pluginApi: null
9+
property var main: null
10+
11+
property var _allEntities: []
12+
property string _searchText: ""
13+
property bool _loading: false
14+
property int _pinVersion: 0
15+
16+
clip: true
17+
18+
function load() {
19+
searchInput.text = ""
20+
root._searchText = ""
21+
_fetchAll()
22+
}
23+
24+
function _isPinned(entity_id) {
25+
const pinned = pluginApi?.pluginSettings?.entities ?? []
26+
return pinned.includes(entity_id)
27+
}
28+
29+
function _togglePin(entity_id) {
30+
let pinned = pluginApi?.pluginSettings?.entities ?? []
31+
pinned = [...pinned]
32+
const idx = pinned.indexOf(entity_id)
33+
if (idx >= 0) {
34+
pinned.splice(idx, 1)
35+
} else {
36+
pinned.push(entity_id)
37+
}
38+
pluginApi.pluginSettings.entities = pinned
39+
pluginApi.saveSettings()
40+
root.main.refreshEntities()
41+
root._pinVersion++
42+
}
43+
44+
ListModel { id: _filteredModel }
45+
46+
function _refilter() {
47+
const q = root._searchText.toLowerCase()
48+
const source = root._allEntities
49+
50+
_filteredModel.clear()
51+
52+
for (const e of source) {
53+
if (!q ||
54+
e.entity_id.toLowerCase().includes(q) ||
55+
e.friendly_name.toLowerCase().includes(q)) {
56+
_filteredModel.append(e)
57+
}
58+
}
59+
}
60+
61+
// Call it when entities load
62+
function _fetchAll() {
63+
root._loading = true
64+
root.main.getAllStates(function(results) {
65+
root._allEntities = results
66+
root._refilter()
67+
root._loading = false
68+
})
69+
}
70+
71+
ColumnLayout {
72+
anchors.fill: parent
73+
spacing: Style.marginM
74+
75+
NTextInput {
76+
id: searchInput
77+
Layout.fillWidth: true
78+
label: "Search"
79+
placeholderText: "Filter by name or entity ID..."
80+
onTextChanged: {
81+
root._searchText = text
82+
root._refilter()
83+
}
84+
}
85+
86+
// Loading state
87+
Item {
88+
Layout.fillWidth: true
89+
Layout.fillHeight: true
90+
visible: root._loading
91+
92+
ColumnLayout {
93+
anchors.centerIn: parent
94+
spacing: Style.marginM
95+
96+
NIcon {
97+
Layout.alignment: Qt.AlignHCenter
98+
icon: "loader"
99+
color: Color.mOnSurfaceVariant
100+
101+
RotationAnimation on rotation {
102+
running: root._loading
103+
loops: Animation.Infinite
104+
from: 0; to: 360
105+
duration: 1000
106+
}
107+
}
108+
109+
NText {
110+
Layout.alignment: Qt.AlignHCenter
111+
text: "Loading entities..."
112+
color: Color.mOnSurfaceVariant
113+
pointSize: Style.fontSizeM
114+
}
115+
}
116+
}
117+
118+
//Entity list
119+
NScrollView {
120+
Layout.fillWidth: true
121+
Layout.fillHeight: true
122+
visible: !root._loading
123+
clip: true
124+
125+
ListView {
126+
width: parent.width
127+
height: parent.height
128+
clip: true
129+
130+
model: _filteredModel
131+
spacing: Style.marginS
132+
133+
delegate: Rectangle {
134+
width: ListView.view.width
135+
height: 56
136+
color: Color.mSurfaceVariant
137+
radius: Style.radiusM
138+
139+
readonly property bool pinned: {
140+
root._pinVersion
141+
return root._isPinned(model.entity_id)
142+
}
143+
144+
RowLayout {
145+
anchors { fill: parent; margins: Style.marginM }
146+
spacing: Style.marginM
147+
148+
ColumnLayout {
149+
Layout.fillWidth: true
150+
spacing: 2
151+
152+
NText {
153+
text: model.friendly_name
154+
color: Color.mOnSurface
155+
pointSize: Style.fontSizeM
156+
elide: Text.ElideRight
157+
Layout.fillWidth: true
158+
}
159+
160+
NText {
161+
text: model.entity_id
162+
color: Color.mOnSurfaceVariant
163+
pointSize: Style.fontSizeS
164+
elide: Text.ElideRight
165+
Layout.fillWidth: true
166+
}
167+
}
168+
169+
NIconButton {
170+
icon: parent.parent.pinned ? "pin-filled" : "pin"
171+
color: parent.parent.pinned ? Color.mTertiary : Color.mOutline
172+
173+
onClicked: root._togglePin(model.entity_id)
174+
}
175+
}
176+
}
177+
}
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)