-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
226 additions
and
181 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,28 @@ | ||
import { | ||
preferences, | ||
} from "@raycast/api"; | ||
import { Connection, createConnection, createLongLivedTokenAuth } from "home-assistant-js-websocket"; | ||
import { HomeAssistant } from "./haapi"; | ||
|
||
export function createHomeAssistantClient() { | ||
const instance = preferences.instance?.value as string; | ||
const token = preferences.token?.value as string; | ||
const ha = new HomeAssistant(instance, token); | ||
return ha; | ||
} | ||
} | ||
|
||
var con: Connection; | ||
|
||
export async function getHAWSConnection() { | ||
if (con) { | ||
console.log("return existing ws con"); | ||
return con; | ||
} else { | ||
console.log("create new home assistant ws con"); | ||
const instance = preferences.instance?.value as string; | ||
const token = preferences.token?.value as string; | ||
const auth = createLongLivedTokenAuth(instance, token); | ||
con = await createConnection({ auth }); | ||
return con; | ||
} | ||
} |
126 changes: 57 additions & 69 deletions
126
extensions/homeassistant/src/components/attributes_all.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,92 @@ | ||
import { ActionPanel, CopyToClipboardAction, List, showToast, ToastStyle } from "@raycast/api"; | ||
import { ActionPanel, CopyToClipboardAction, List, ListItem, showToast, ToastStyle } from "@raycast/api"; | ||
import { useState, useEffect } from "react"; | ||
import { createHomeAssistantClient } from "../common"; | ||
import { State } from "../haapi"; | ||
import { useHAStates } from "../hooks"; | ||
|
||
export const ha = createHomeAssistantClient(); | ||
|
||
class Attribute { | ||
public name = ""; | ||
public value: any; | ||
} | ||
|
||
export function StatesAttributesList(props: { domain: string }) { | ||
const [searchText, setSearchText] = useState<string>(); | ||
const { attributes, error, isLoading } = useSearch(searchText, props.domain); | ||
const { states: allStates, error, isLoading } = useHAStates(); | ||
const { states } = useSearch(searchText, allStates); | ||
|
||
if (error) { | ||
showToast(ToastStyle.Failure, "Cannot search Home Assistant states", error); | ||
} | ||
|
||
if (!attributes) { | ||
if (!states) { | ||
return <List isLoading={true} searchBarPlaceholder="Loading" />; | ||
} | ||
|
||
const stateTitle = (state: State): string => { | ||
const attrs = state.attributes; | ||
return attrs.friendly_name ? `${attrs.friendly_name} (${state.entity_id})` : state.entity_id; | ||
}; | ||
|
||
return ( | ||
<List | ||
searchBarPlaceholder="Filter by entity ID or attribute name" | ||
isLoading={isLoading} | ||
onSearchTextChange={setSearchText} | ||
> | ||
{attributes?.map((attr) => ( | ||
<List.Item | ||
key={attr.name} | ||
title={attr.name} | ||
accessoryTitle={`${attr.value}`.substring(0, 50)} | ||
actions={ | ||
<ActionPanel> | ||
<CopyToClipboardAction content={`${attr.value}`} /> | ||
</ActionPanel> | ||
} | ||
/> | ||
{states.map((state: State) => ( | ||
<List.Section key={state.entity_id} title={stateTitle(state)}> | ||
<List.Item key={`${state.entity_id}_state`} title="state" accessoryTitle={`${state.state}`} /> | ||
{Object.entries(state.attributes).map(([k, v]) => ( | ||
<List.Item | ||
key={state.entity_id + k} | ||
title={k} | ||
accessoryTitle={`${v}`} | ||
actions={ | ||
<ActionPanel> | ||
<CopyToClipboardAction title="Copy Value" content={`${v}`} /> | ||
<CopyToClipboardAction title="Copy Name" content={`${k}`} /> | ||
<CopyToClipboardAction title="Copy Entity ID" content={`${state.entity_id}`} /> | ||
</ActionPanel> | ||
} | ||
/> | ||
))} | ||
</List.Section> | ||
))} | ||
</List> | ||
); | ||
} | ||
|
||
export function useSearch( | ||
function useSearch( | ||
query: string | undefined, | ||
domain: string | ||
allStates?: State[] | ||
): { | ||
attributes?: Attribute[]; | ||
error?: string; | ||
isLoading: boolean; | ||
states?: State[]; | ||
} { | ||
const [attributes, setAttributes] = useState<Attribute[]>(); | ||
const [error, setError] = useState<string>(); | ||
const [isLoading, setIsLoading] = useState<boolean>(false); | ||
|
||
let cancel = false; | ||
const [states, setStates] = useState<State[]>(); | ||
const lquery = query ? query.toLocaleLowerCase().trim() : query; | ||
|
||
useEffect(() => { | ||
async function fetchData() { | ||
if (query === null || cancel) { | ||
return; | ||
} | ||
|
||
setIsLoading(true); | ||
setError(undefined); | ||
|
||
try { | ||
const haStates = await ha.getStates({ domain: domain, query: "" }); | ||
let attributesData: Attribute[] = []; | ||
haStates.forEach((e) => | ||
Object.entries(e.attributes).forEach(([k, v]) => | ||
attributesData.push({ name: `${e.entity_id}.${k}`, value: v }) | ||
) | ||
); | ||
if (query) { | ||
const lquery = query.toLocaleLowerCase().trim(); | ||
attributesData = attributesData.filter((v) => v.name.toLocaleLowerCase().includes(lquery)); | ||
} | ||
attributesData = attributesData.slice(0, 100); | ||
if (!cancel) { | ||
setAttributes(attributesData); | ||
} | ||
} catch (e: any) { | ||
if (!cancel) { | ||
setError(e.toString()); | ||
if (allStates) { | ||
let filteredStates: State[] = []; | ||
allStates.forEach((s) => { | ||
let attrs: Record<string, any> = {}; | ||
for (const [k, v] of Object.entries(s.attributes)) { | ||
if (lquery) { | ||
const friendlyName: string = (s.attributes.friendly_name || "").toLocaleLowerCase(); | ||
const eid = `${s.entity_id}.${k}`.toLocaleLowerCase(); | ||
if (eid.includes(lquery) || friendlyName.includes(lquery)) { | ||
attrs[k] = v; | ||
} | ||
} else { | ||
attrs[k] = v; | ||
} | ||
} | ||
} finally { | ||
if (!cancel) { | ||
setIsLoading(false); | ||
if (Object.keys(attrs).length > 0) { | ||
const ns: State = { entity_id: s.entity_id, state: s.state, attributes: attrs }; | ||
filteredStates.push(ns); | ||
} | ||
} | ||
}); | ||
setStates(filteredStates.slice(0, 100)); | ||
} else { | ||
setStates([]); | ||
} | ||
|
||
fetchData(); | ||
|
||
return () => { | ||
cancel = true; | ||
}; | ||
}, [query]); | ||
|
||
return { attributes, error, isLoading }; | ||
}, [query, allStates]); | ||
return { states }; | ||
} |
Oops, something went wrong.