Skip to content

Commit

Permalink
Add Groups page, group components
Browse files Browse the repository at this point in the history
  • Loading branch information
amberstarlight committed Apr 18, 2024
1 parent bca5cb5 commit 2803d99
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 32 deletions.
24 changes: 16 additions & 8 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import { StyledText, lightTheme, darkTheme } from "./utils/theme";

import Devices from "./pages/Devices/Devices";
import Settings from "./pages/Settings/Settings";
import Groups from "./pages/Groups/Groups";

import Button from "./components/Button/Button";
import DeviceSettings from "./components/DeviceSettings/DeviceSettings";
import { type Device, type Group } from "../../types/zigbee_types";

const Wrapper = styled.div`
padding: 2em;
Expand All @@ -30,7 +32,7 @@ const backend = import.meta.env.VITE_API_URL ?? "";

function App() {
const [devices, setDevices] = useState();
const [bridgeState, setBridgeState] = useState();
const [groups, setGroups] = useState();
const [theme] = useDarkMode();

const themeMode = theme === "light" ? lightTheme : darkTheme;
Expand All @@ -39,6 +41,10 @@ function App() {
fetch(`${backend}/api/devices`)
.then((res) => res.json())
.then((data) => setDevices(data.data));

fetch(`${backend}/api/groups`)
.then((res) => res.json())
.then((data) => setGroups(data.data));
}, []);

return (
Expand All @@ -62,21 +68,19 @@ function App() {
<Devices devices={devices} />
</Route>

<Route path={"/devices/:friendlyName"}>
<Route path={"/devices/:deviceId"}>
{(params) => {
if (!devices) return <></>;

const device = devices.find(
(device) =>
Object.prototype.hasOwnProperty.call(device, "friendly_name") &&
device.friendly_name ===
decodeURIComponent(params.friendlyName),
(device: Device) =>
device.ieee_address === decodeURIComponent(params.deviceId),
);

if (!device)
return (
<StyledText>
Device <b>{params.friendlyName}</b> does not exist on this
Device <code>{params.deviceId}</code> does not exist on this
network.
</StyledText>
);
Expand All @@ -85,8 +89,12 @@ function App() {
}}
</Route>

<Route path={"/groups"}>
<Groups groups={groups} />
</Route>

<Route path={"/settings"}>
<Settings bridgeState={bridgeState} />
<Settings bridgeState={{}} />
</Route>
</Wrapper>
</ThemeProvider>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/DeviceCard/DeviceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { deviceDescription } from "../../utils/deviceUtilities";
import styled from "styled-components";
import { StyledText, StyledHeader } from "../../utils/theme";

import { type Device } from "../../../../types/zigbee_types";

const emojiLookup = {
light: "💡",
switch: "🔌",
Expand All @@ -25,7 +27,7 @@ const Card = styled.div`
}
`;

function DeviceCard(props) {
function DeviceCard(props: { device: Device; onClick: Function }) {
let deviceEmoji = "❓";
let deviceDefinition = props.device.definition;

Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/DeviceList/DeviceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@

import { Link } from "wouter";
import DeviceCard from "../DeviceCard/DeviceCard";
import { type Device } from "../../../../types/zigbee_types";

function DeviceList(props) {
function DeviceList(props: { devices: Device[]; onClick: Function }) {
return (
<div>
{props.devices.map((device) => (
{props.devices.map((device: Device) => (
<Link
href={`/devices/${device.friendly_name}`}
href={`/devices/${device.ieee_address}`}
key={device.ieee_address}
>
<DeviceCard device={device} onClick={() => props.onClick} />
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/DeviceSettings/DeviceSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { deviceSettingsGenerator } from "./generator";
import EditableText from "../EditableText/EditableText";
import { deviceDescription } from "../../utils/deviceUtilities";

const backend = import.meta.env.VITE_API_URL ?? "";

function DeviceSettings(props) {
const [deviceSettingsState, setDeviceSettingsState] = useState();
const [deviceFriendlyNameState, setDeviceFriendlyNameState] = useState(
Expand All @@ -26,9 +28,9 @@ function DeviceSettings(props) {
properties[property.name] = "";
});

getDeviceSettings(props.device.friendly_name, properties).then(
setDeviceSettingsState,
);
fetch(`${backend}/api/devices/${props.device.ieee_address}/state`)
.then((res) => res.json())
.then((data) => setDeviceSettingsState(data.data));
}, []);

if (!deviceSettingsState) return <LoadingSpinner />;
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/DeviceSettings/generator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later

import {
mqttStateToBoolean,
booleanToMqttState,
hexToRGB,
hslToRGB,
Expand All @@ -10,6 +11,7 @@ import {
} from "../../utils/deviceUtilities";
import { numericTransformer } from "../../utils/transformers";
import Toggle from "../Toggle/Toggle";
import ColorPicker from "../ColorPicker/ColorPicker";

export const deviceSettingsGenerator = (
device,
Expand All @@ -30,9 +32,7 @@ export const deviceSettingsGenerator = (
onChange={(event) => {
const newMqttState = booleanToMqttState(event.target.checked);
updateDeviceState(
deviceSettingsState,
setDeviceSettingsState,
device.friendly_name,
device.ieee_address,
feature.name,
newMqttState,
);
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/components/GroupCard/GroupCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: © 2024 Amber Cronin <software@amber.vision>
// SPDX-License-Identifier: AGPL-3.0-or-later

import styled from "styled-components";
import { StyledText, StyledHeader } from "../../utils/theme";

import { type Group } from "../../../../types/zigbee_types";

const Card = styled.div`
margin: 2em 0em;
padding: 1em;
cursor: pointer;
border-radius: 2rem;
&:hover {
background-color: ${({ theme }) => theme.hover};
}
`;

function GroupCard(props: { group: Group; onClick: Function }) {
return (
<Card onClick={props.onClick}>
<StyledHeader>{props.group.friendly_name}</StyledHeader>
<StyledText>{`${props.group.members.length} devices`}</StyledText>
</Card>
);
}

export default GroupCard;
20 changes: 20 additions & 0 deletions frontend/src/components/GroupList/GroupList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: © 2024 Amber Cronin <software@amber.vision>
// SPDX-License-Identifier: AGPL-3.0-or-later

import { Link } from "wouter";
import GroupCard from "../GroupCard/GroupCard";
import { type Group } from "../../../../types/zigbee_types";

function GroupList(props: { groups: Group[]; onClick?: Function }) {
return (
<div>
{props.groups.map((group: Group) => (
<Link href={`/groups/${group.id}`} key={group.id}>
<GroupCard group={group} onClick={() => props.onClick} />
</Link>
))}
</div>
);
}

export default GroupList;
23 changes: 23 additions & 0 deletions frontend/src/pages/Groups/Groups.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: © 2024 Amber Cronin <software@amber.vision>
// SPDX-License-Identifier: AGPL-3.0-or-later

import LoadingSpinner from "../../components/LoadingSpinner/LoadingSpinner";
import GroupList from "../../components/GroupList/GroupList";

function Groups(props) {
const selectedGroup = props.selectedGroup;

let groupContent = undefined;

if (!props.groups || !selectedGroup) groupContent = <LoadingSpinner />;

if (selectedGroup) groupContent = <GroupSettings group={selectedGroup} />;

if (props.groups && !selectedGroup) {
groupContent = <GroupList groups={props.groups} />;
}

return <>{groupContent !== undefined ? groupContent : ""}</>;
}

export default Groups;
34 changes: 20 additions & 14 deletions frontend/src/utils/deviceUtilities.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
// SPDX-FileCopyrightText: © 2021 Amber Cronin <software@amber.vision>
// SPDX-License-Identifier: AGPL-3.0-or-later

export const mqttStateToBoolean = (state) => {
const backend = import.meta.env.VITE_API_URL ?? "";

export const mqttStateToBoolean = (state: string): boolean => {
if (state === "ON") return true;
return false;
};

export const booleanToMqttState = (boolean) => {
export const booleanToMqttState = (boolean: boolean): string => {
if (boolean) return "ON";
return "OFF";
};

export const updateDeviceState = (
deviceSettingsState,
setDeviceSettingsState,
deviceFriendlyName,
property,
value,
deviceId: string,
property: string,
value: any,
) => {
const updateObject = {};
updateObject[property] = value;
setDeviceSettings(deviceFriendlyName, updateObject);

const clonedState = { ...deviceSettingsState };
clonedState[property] = value;
setDeviceSettingsState(clonedState);
const request = new Request(`${backend}/api/devices/${deviceId}/state`, {
method: "POST",
mode: "cors",
headers: {
"content-type": "application/json",
},
body: JSON.stringify({
setting: property,
value: value,
}),
});

fetch(request).then();
};

export const deviceDescription = (deviceDefinition) => {
Expand Down

0 comments on commit 2803d99

Please sign in to comment.