Automation.js 6.8 KB

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