@@ -11,6 +11,7 @@ import {
1111 Color ,
1212 getPreferenceValues ,
1313 Icon ,
14+ LocalStorage ,
1415 LaunchProps ,
1516 List ,
1617 showToast ,
@@ -35,6 +36,23 @@ type PreferencesState = {
3536 accessToken : string ;
3637} ;
3738
39+ const FAVORITE_LIGHTS_KEY = "homeAssistantFavoriteLights" ;
40+
41+ async function loadFavoriteLights ( ) : Promise < string [ ] > {
42+ const stored = await LocalStorage . getItem < string > ( FAVORITE_LIGHTS_KEY ) ;
43+ if ( ! stored ) return [ ] ;
44+
45+ try {
46+ return JSON . parse ( stored ) as string [ ] ;
47+ } catch {
48+ return [ ] ;
49+ }
50+ }
51+
52+ async function saveFavoriteLights ( favorites : string [ ] ) : Promise < void > {
53+ await LocalStorage . setItem ( FAVORITE_LIGHTS_KEY , JSON . stringify ( favorites ) ) ;
54+ }
55+
3856function formatBrightness ( brightness ?: number ) : string | null {
3957 if ( brightness === undefined || Number . isNaN ( brightness ) ) return null ;
4058 const percent = Math . round ( ( brightness / 255 ) * 100 ) ;
@@ -45,6 +63,10 @@ function friendlyName(light: LightState): string {
4563 return light . attributes . friendly_name || light . entity_id ;
4664}
4765
66+ function wait ( ms : number ) : Promise < void > {
67+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
68+ }
69+
4870function getLightAccessories ( light : LightState ) : { text : string } [ ] {
4971 const accessories : { text : string } [ ] = [ { text : light . state } ] ;
5072 const brightness = formatBrightness ( light . attributes . brightness ) ;
@@ -128,6 +150,17 @@ function HomeAssistantLightsContent({ fallbackText }: { fallbackText?: string })
128150 placeholderData : keepPreviousData ,
129151 } ) ;
130152
153+ const { data : favoriteLights = [ ] } = useQuery ( {
154+ queryKey : [ "home-assistant" , "lights" , "favorites" ] ,
155+ queryFn : loadFavoriteLights ,
156+ staleTime : Infinity ,
157+ } ) ;
158+
159+ const favoriteSet = useMemo (
160+ ( ) => new Set ( favoriteLights ) ,
161+ [ favoriteLights ] ,
162+ ) ;
163+
131164 useEffect ( ( ) => {
132165 if ( isError && error ) {
133166 const message =
@@ -147,28 +180,69 @@ function HomeAssistantLightsContent({ fallbackText }: { fallbackText?: string })
147180 const sorted = [ ...lights ] . sort ( ( a , b ) =>
148181 friendlyName ( a ) . localeCompare ( friendlyName ( b ) ) ,
149182 ) ;
150- if ( ! query ) return sorted ;
183+ if ( ! query ) {
184+ return sorted . sort ( ( a , b ) => {
185+ const aFavorite = favoriteSet . has ( a . entity_id ) ;
186+ const bFavorite = favoriteSet . has ( b . entity_id ) ;
187+ if ( aFavorite === bFavorite ) {
188+ return friendlyName ( a ) . localeCompare ( friendlyName ( b ) ) ;
189+ }
190+ return aFavorite ? - 1 : 1 ;
191+ } ) ;
192+ }
151193 return sorted . filter ( ( light ) => {
152194 const name = friendlyName ( light ) . toLowerCase ( ) ;
153195 return name . includes ( query ) || light . entity_id . toLowerCase ( ) . includes ( query ) ;
154196 } ) ;
155- } , [ lights , searchText ] ) ;
197+ } , [ lights , searchText , favoriteSet ] ) ;
198+
199+ async function toggleFavorite ( light : LightState ) : Promise < void > {
200+ const isFavorite = favoriteSet . has ( light . entity_id ) ;
201+ const updated = isFavorite
202+ ? favoriteLights . filter ( ( entityId ) => entityId !== light . entity_id )
203+ : [ light . entity_id , ...favoriteLights ] ;
204+
205+ await saveFavoriteLights ( updated ) ;
206+ queryClient . setQueryData (
207+ [ "home-assistant" , "lights" , "favorites" ] ,
208+ updated ,
209+ ) ;
210+ await showToast ( {
211+ style : Toast . Style . Success ,
212+ title : isFavorite ? "Removed from favorites" : "Added to favorites" ,
213+ message : friendlyName ( light ) ,
214+ } ) ;
215+ }
156216
157217 async function handleLightAction (
158218 light : LightState ,
159219 service : "turn_on" | "turn_off" | "toggle" ,
160220 label : string ,
161221 ) : Promise < void > {
162222 try {
223+ const expectedState =
224+ service === "turn_on"
225+ ? "on"
226+ : service === "turn_off"
227+ ? "off"
228+ : light . state === "on"
229+ ? "off"
230+ : "on" ;
163231 await callLightService ( service , light . entity_id , preferences ) ;
164232 await showToast ( {
165233 style : Toast . Style . Success ,
166234 title : label ,
167235 message : friendlyName ( light ) ,
168236 } ) ;
169- await queryClient . invalidateQueries ( {
170- queryKey : [ "home-assistant" , "lights" ] ,
171- } ) ;
237+ for ( const delay of [ 0 , 200 , 500 , 900 ] ) {
238+ if ( delay > 0 ) await wait ( delay ) ;
239+ const updated = await fetchLights ( preferences ) ;
240+ queryClient . setQueryData ( [ "home-assistant" , "lights" ] , updated ) ;
241+ const matched = updated . find (
242+ ( item ) => item . entity_id === light . entity_id ,
243+ ) ;
244+ if ( matched ?. state === expectedState ) break ;
245+ }
172246 } catch ( requestError ) {
173247 await showToast ( {
174248 style : Toast . Style . Failure ,
@@ -218,15 +292,24 @@ function HomeAssistantLightsContent({ fallbackText }: { fallbackText?: string })
218292 icon = { getLightIcon ( light ) }
219293 accessories = { getLightAccessories ( light ) }
220294 detail = { < LightDetail light = { light } /> }
221- actions = {
222- < ActionPanel >
223- < Action
224- title = "Toggle Light"
225- icon = { Icon . Switch }
226- onAction = { ( ) =>
227- handleLightAction ( light , "toggle" , "Light toggled" )
228- }
229- />
295+ actions = {
296+ < ActionPanel >
297+ < Action
298+ title = "Toggle Light"
299+ icon = { Icon . Switch }
300+ onAction = { ( ) =>
301+ handleLightAction ( light , "toggle" , "Light toggled" )
302+ }
303+ />
304+ < Action
305+ title = {
306+ favoriteSet . has ( light . entity_id )
307+ ? "Remove from Favorites"
308+ : "Add to Favorites"
309+ }
310+ icon = { Icon . Pin }
311+ onAction = { ( ) => toggleFavorite ( light ) }
312+ />
230313 < ActionPanel . Section >
231314 < Action
232315 title = "Turn On"
0 commit comments