Skip to content

Commit 49c10a6

Browse files
committed
feat: add favorite lights functionality
1 parent 56c80f1 commit 49c10a6

File tree

5 files changed

+119
-15
lines changed

5 files changed

+119
-15
lines changed
-23.2 KB
Loading
Lines changed: 20 additions & 0 deletions
Loading

.config/vicinae/extensions/home-assistant/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"commands": [
1616
{
1717
"name": "home-assistant-lights",
18-
"title": "Home Assistant Lights",
18+
"title": "Lights",
1919
"subtitle": "Control Home Assistant lights",
2020
"description": "List and control Home Assistant lights",
2121
"mode": "view"

.config/vicinae/extensions/home-assistant/src/home-assistant-lights.tsx

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
3856
function 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+
4870
function 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"

.config/vicinae/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"opacity": 0.88
2828
},
2929
"favorites": [
30+
"@fbosch/home-assistant:home-assistant-lights",
3031
"applications:com.discordapp.Discord",
3132
"@aurelleb/store.vicinae.mullvad:connect",
3233
"@aurelleb/store.vicinae.mullvad:disconnect",

0 commit comments

Comments
 (0)