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