Automation.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { useEffect, useState } from "react";
  2. import { Button, Collapse, Drawer, Icon, Steps, Typography } from "tiny-ui";
  3. import WLED from "./WLED.js";
  4. import { useLocalStorage } from "lib/state";
  5. import { isBlank } from "lib/utils";
  6. const { Step } = Steps;
  7. const { Panel } = Collapse;
  8. const { Heading, Paragraph, Text } = Typography;
  9. const Client = ({ name, client, connection, ...rest }) => {
  10. const normalize = (v) => JSON.stringify(v, null, 2);
  11. return (
  12. <Panel header={name} {...rest}>
  13. <p>IP address: {client}</p>
  14. <div style={{ overflowY: "scroll", height: 200 }}>
  15. {Object.keys(connection?.state)?.map((o, i) => {
  16. return (
  17. <div>
  18. <strong>{o}:</strong> {normalize(connection?.state?.[o])}
  19. </div>
  20. );
  21. })}
  22. </div>
  23. </Panel>
  24. );
  25. };
  26. const WLEDClient = WLED(Client);
  27. const Automation = ({
  28. automation,
  29. presets,
  30. clients,
  31. names,
  32. onPreset,
  33. onSync,
  34. onPower,
  35. connections,
  36. ...props
  37. }) => {
  38. const [current, setCurrent] = useLocalStorage("preset", -1, 3600);
  39. const [total, setTotal] = useState();
  40. const [help, setHelp] = useState(false);
  41. const [info, setInfo] = useState(false);
  42. const getTitle = (indx) => presets?.[indx]?.name || indx;
  43. const getName = (v) => {
  44. let indx = clients?.findIndex((o) => o === v);
  45. return indx > -1 ? names?.[indx] : v;
  46. };
  47. const getDescription = (indx) => {
  48. return (
  49. (automation?.[indx] &&
  50. Object.keys(automation?.[indx])
  51. ?.map((o) => getName(o))
  52. ?.join(", ")) ||
  53. null
  54. );
  55. };
  56. const handleChange = (indx) => {
  57. let keys = Object.keys(automation?.[indx]);
  58. let preset = { clients: keys || [], value: indx };
  59. let power = { on: [], off: [] };
  60. let sync = { enable: [], disable: [] };
  61. // loop the keys and determine what to do
  62. for (let key of keys) {
  63. if (automation?.[indx]?.[key]?.on === true) power.on.push(key);
  64. else power.off.push(key);
  65. if (automation?.[indx]?.[key]?.sync === true) sync.enable.push(key);
  66. else sync.disable.push(key);
  67. }
  68. if (!isBlank(sync?.enable))
  69. onSync({ clients: sync?.enable, value: "enable" }); // enable sync
  70. if (!isBlank(sync?.disable))
  71. onSync({ clients: sync?.disable, value: "disable" }); // disable sync
  72. setTimeout(() => {
  73. onPreset(preset); // apply the preset
  74. }, 100);
  75. setTimeout(() => {
  76. if (!isBlank(power?.on)) onPower({ clients: power?.on, value: "on" }); // power on
  77. if (!isBlank(power?.off)) onPower({ clients: power?.off, value: "off" }); // power off
  78. }, 200);
  79. };
  80. const handlePower = (value) => {
  81. let keys = Object.keys(automation?.[1]);
  82. onPower({ clients: keys, value: value });
  83. };
  84. useEffect(() => {
  85. setTotal((automation && Object.keys(automation)?.length) || 0);
  86. }, []);
  87. useEffect(() => {
  88. if (current > -1 && current < total) {
  89. handleChange(current + 1);
  90. }
  91. }, [current]);
  92. let steps = Object.keys(automation).map((o, i) => {
  93. return (
  94. <Step
  95. key={i}
  96. title={
  97. <div style={{ display: "flex", flexDirection: "row" }}>
  98. <div>{getTitle(o)}</div>
  99. {current === o - 1 ? (
  100. <div style={{ marginLeft: "3rem" }}>
  101. <Button
  102. title="Reapply"
  103. round
  104. icon={<Icon name="loader-circle" />}
  105. onClick={() => {
  106. handleChange(o);
  107. }}
  108. />
  109. </div>
  110. ) : null}
  111. </div>
  112. }
  113. description={getDescription(o)}
  114. ></Step>
  115. );
  116. });
  117. return (
  118. <>
  119. <div style={{ position: "sticky", top: "60px", marginBottom: "2rem" }}>
  120. <Button.Group size="md" round>
  121. <Button
  122. title="Previous"
  123. icon={<Icon name="arrow-left" />}
  124. onClick={() => {
  125. let next = current - 1;
  126. if (next < 0) next = 0;
  127. setCurrent(next);
  128. }}
  129. />
  130. <Button
  131. title="Reapply"
  132. icon={<Icon name="loader-circle" />}
  133. onClick={() => {
  134. handleChange(current + 1);
  135. }}
  136. />
  137. <Button
  138. title="Next"
  139. icon={<Icon name="arrow-right" />}
  140. onClick={() => {
  141. let next = current + 1;
  142. if (next > total) next = 0;
  143. setCurrent(next);
  144. }}
  145. />
  146. </Button.Group>
  147. <Button.Group size="md" round>
  148. <Button
  149. title="Power on"
  150. onClick={() => {
  151. handlePower("on");
  152. }}
  153. >
  154. On
  155. </Button>
  156. <Button
  157. title="Power off"
  158. onClick={() => {
  159. handlePower("off");
  160. }}
  161. >
  162. Off
  163. </Button>
  164. </Button.Group>
  165. <Button.Group size="md" round>
  166. <Button
  167. title="Help"
  168. icon={<Icon name="question-fill" />}
  169. onClick={() => {
  170. setHelp(true);
  171. }}
  172. />
  173. </Button.Group>
  174. <Button.Group size="md" round>
  175. <Button
  176. title="Information"
  177. icon={<Icon name="info" />}
  178. onClick={() => {
  179. setInfo(true);
  180. }}
  181. />
  182. </Button.Group>
  183. </div>
  184. <Steps
  185. current={current}
  186. direction="vertical"
  187. onChange={(v) => setCurrent(v)}
  188. >
  189. {steps}
  190. </Steps>
  191. <Drawer
  192. header="Client connections"
  193. placement="right"
  194. size={480}
  195. onClose={() => setInfo(false)}
  196. visible={info}
  197. >
  198. <Collapse accordion bordered={false}>
  199. {connections?.map((o, i) => (
  200. <WLEDClient
  201. key={i}
  202. itemKey={i}
  203. connection={o}
  204. client={clients[i]}
  205. name={names[i]}
  206. />
  207. ))}
  208. </Collapse>
  209. </Drawer>
  210. <Drawer
  211. header="Help"
  212. placement="right"
  213. size={480}
  214. onClose={() => setHelp(false)}
  215. visible={help}
  216. >
  217. <>
  218. <Paragraph>
  219. Select one of the presets from the left to start applying preset
  220. automation.
  221. </Paragraph>
  222. <Paragraph>
  223. You can use the navigation buttons above to advance forward{" "}
  224. <Icon name="arrow-right" size={16} /> and backward{" "}
  225. <Icon name="arrow-left" size={16} /> through the preset list.
  226. </Paragraph>
  227. <Paragraph>
  228. If a preset fails to apply to all of the intended WLED instances
  229. then use the Reapply button <Icon name="loader-circle" size={16} />{" "}
  230. within the navigation menu bar or within the target step.
  231. </Paragraph>
  232. </>
  233. </Drawer>
  234. </>
  235. );
  236. };
  237. export default Automation;