Management.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import { useEffect, useRef, useState } from "react";
  2. import styled from "styled-components";
  3. import {
  4. Button,
  5. Col,
  6. Collapse,
  7. Icon,
  8. Menu,
  9. Modal,
  10. PopConfirm,
  11. Row,
  12. ScrollIndicator,
  13. Slider,
  14. SplitButton,
  15. Transfer
  16. } from "tiny-ui";
  17. import WLED from "./WLED.js";
  18. const { Panel } = Collapse;
  19. const to255 = (v) => Math.ceil((v / 100) * 255);
  20. const from255 = (v) => Math.ceil((v / 255) * 100);
  21. const download = (filename, data, type = "application/json") => {
  22. const blob = new Blob([data], { type: type });
  23. const href = URL.createObjectURL(blob);
  24. const link = document.createElement("a");
  25. link.href = href;
  26. link.download = filename;
  27. document.body.appendChild(link);
  28. link.click();
  29. document.body.removeChild(link);
  30. };
  31. const StyledRow = styled((props) => <Row {...props} />)`
  32. margin: 0.25rem 0.5rem;
  33. & .action {
  34. text-align: right;
  35. & .ty-icon {
  36. margin-right: 1rem;
  37. }
  38. }
  39. `;
  40. const StyledMenu = styled((props) => <Menu {...props} />)`
  41. & .ty-icon {
  42. margin-right: 1rem;
  43. }
  44. `;
  45. const ScrollingDiv = (props) => {
  46. const targetRef = useRef(null);
  47. return (
  48. <>
  49. <ScrollIndicator fixed={false} target={() => targetRef?.current} />
  50. <div
  51. ref={targetRef}
  52. style={{ overflowY: "scroll", height: 200 }}
  53. {...props}
  54. />
  55. </>
  56. );
  57. };
  58. const UploadModal = ({ data, client, onConfirm, ...rest }) => {
  59. const [payload, setPayload] = useState({});
  60. useEffect(() => {
  61. if (data) setPayload({ ...data });
  62. }, [data]);
  63. const handleConfirm = (_) => {
  64. onConfirm(payload);
  65. };
  66. const handleChange = (e) => {
  67. const fileReader = new FileReader();
  68. fileReader.readAsText(e?.target?.files?.[0], "UTF-8");
  69. fileReader.onload = (e) => {
  70. if (e?.target?.result) {
  71. console.log("payload", {
  72. ...payload,
  73. value: JSON.parse(e?.target?.result)
  74. });
  75. setPayload({ ...payload, value: JSON.parse(e?.target?.result) });
  76. }
  77. };
  78. };
  79. if (!data) return null;
  80. return (
  81. <Modal
  82. header="Upload preset"
  83. visible={true}
  84. onConfirm={handleConfirm}
  85. {...rest}
  86. >
  87. <h5>Are you sure you want to upload and override presets?</h5>
  88. {payload?.value ? (
  89. <ScrollingDiv>
  90. <pre>{JSON.stringify(payload?.value, null, 2)}</pre>
  91. </ScrollingDiv>
  92. ) : null}
  93. <input type="file" onChange={handleChange} />
  94. </Modal>
  95. );
  96. };
  97. const PurgeModal = ({ data, client, onConfirm, ...rest }) => {
  98. const [payload, setPayload] = useState({});
  99. useEffect(() => {
  100. if (data) setPayload({ ...data });
  101. }, [data]);
  102. const handleConfirm = (_) => {
  103. onConfirm(payload);
  104. };
  105. if (!data) return null;
  106. return (
  107. <Modal
  108. header="Delete preset"
  109. visible={true}
  110. onConfirm={handleConfirm}
  111. {...rest}
  112. >
  113. <h5>Are you sure you want to delete these presets?</h5>
  114. <ScrollingDiv>
  115. <pre>{JSON.stringify(data?.value, null, 2)}</pre>
  116. </ScrollingDiv>
  117. </Modal>
  118. );
  119. };
  120. const CopyModal = ({ data, client, clients, onConfirm, ...rest }) => {
  121. const [transfer, setTransfer] = useState({});
  122. const [payload, setPayload] = useState({});
  123. useEffect(() => {
  124. setPayload({ ...payload, value: data });
  125. }, [data]);
  126. useEffect(() => {
  127. if (clients)
  128. setTransfer(
  129. clients
  130. ?.filter((o) => o !== client)
  131. ?.map((o, i) => ({ key: o, label: o }))
  132. );
  133. }, [clients]);
  134. const handleConfirm = (_) => {
  135. onConfirm(payload);
  136. };
  137. if (!data) return null;
  138. return (
  139. <Modal
  140. header="Copy preset"
  141. visible={true}
  142. onConfirm={handleConfirm}
  143. {...rest}
  144. >
  145. <ScrollingDiv>
  146. <pre>{JSON.stringify(data, null, 2)}</pre>
  147. </ScrollingDiv>
  148. <Row>
  149. <Col>
  150. <Transfer
  151. dataSource={transfer}
  152. defaultValue={[]}
  153. onChange={(value) => {
  154. setPayload({ ...payload, clients: value });
  155. }}
  156. />
  157. </Col>
  158. </Row>
  159. </Modal>
  160. );
  161. };
  162. const InstanceManagement = ({
  163. client,
  164. presets,
  165. onCopy,
  166. onPreset,
  167. onDelete
  168. }) => {
  169. if (!presets) return null;
  170. return Object.keys(presets)?.map((o, i) => (
  171. <StyledRow key={i}>
  172. <Col span={16}>{presets[o]?.name || ""}</Col>
  173. <Col span={8} className="action">
  174. <SplitButton
  175. btnType="primary"
  176. title="Apply"
  177. overlay={
  178. <StyledMenu>
  179. <Menu.Item
  180. onClick={(e) => {
  181. download(
  182. `preset_${o}.json`,
  183. JSON.stringify(presets[o], null, 2)
  184. );
  185. }}
  186. >
  187. <Icon name="download" />
  188. Save
  189. </Menu.Item>
  190. <Menu.Item
  191. onClick={(e) => {
  192. onCopy({ [o]: presets[o] });
  193. }}
  194. >
  195. <Icon name="inspection" />
  196. Copy
  197. </Menu.Item>
  198. <Menu.Divider />
  199. <Menu.Item
  200. onClick={(e) => onDelete({ clients: [client], value: [o] })}
  201. >
  202. <Icon name="trash" />
  203. Delete
  204. </Menu.Item>
  205. </StyledMenu>
  206. }
  207. onClick={(e) => {
  208. onPreset({ clients: [client], value: o });
  209. }}
  210. >
  211. <Icon name="checkmark" />
  212. </SplitButton>
  213. </Col>
  214. </StyledRow>
  215. ));
  216. };
  217. const StateMangement = ({
  218. client,
  219. setPurge,
  220. setUpload,
  221. setCopy,
  222. onPower,
  223. onSync,
  224. onBrightness,
  225. onPreset,
  226. onDefaults,
  227. current,
  228. ...props
  229. }) => {
  230. if (!current) return null;
  231. return (
  232. <Row>
  233. <Col span={12}>
  234. <Button
  235. btnType={current?.on ? "primary" : "outline"}
  236. icon={<Icon name="switch" />}
  237. title={`Lights ${current?.on ? "on" : "off"}`}
  238. round
  239. onClick={() =>
  240. onPower({ clients: [client], value: current?.on ? "off" : "on" })
  241. }
  242. />
  243. {current?.udpSync?.send ? (
  244. <Button
  245. onClick={() => onSync({ clients: [client], value: "disable" })}
  246. title="Disable sync"
  247. icon={<Icon name="broadcast" />}
  248. btnType="primary"
  249. round
  250. />
  251. ) : (
  252. <Button
  253. onClick={() => onSync({ clients: [client], value: "enable" })}
  254. btnType="outline"
  255. title="Enable sync"
  256. icon={<Icon name="broadcast" />}
  257. round
  258. />
  259. )}
  260. <PopConfirm
  261. title="Are you sure you want to reboot?"
  262. onConfirm={() => onPower({ clients: [client], value: "reboot" })}
  263. >
  264. <Button
  265. title="Reboot"
  266. icon={<Icon name="loader" />}
  267. btnType="danger"
  268. round
  269. />
  270. </PopConfirm>
  271. </Col>
  272. <Col span={12} style={{ textAlign: "right" }}>
  273. <Button.Group round>
  274. <Button
  275. btnType="secondary"
  276. icon={<Icon name="arrow-left" />}
  277. title="Previous preset"
  278. round
  279. onClick={() => {
  280. onPreset({ clients: [client], value: 249 });
  281. }}
  282. />
  283. <Button
  284. btnType="secondary"
  285. icon={<Icon name="arrow-right" />}
  286. title="Next preset"
  287. round
  288. onClick={() => {
  289. onPreset({ clients: [client], value: 250 });
  290. }}
  291. />
  292. </Button.Group>
  293. <SplitButton
  294. onClick={(e) => {
  295. e.preventDefault();
  296. e.stopPropagation();
  297. window.open(`http://${client}`);
  298. }}
  299. overlay={
  300. <StyledMenu>
  301. <Menu.Item
  302. onClick={(e) => {
  303. e.preventDefault();
  304. e.stopPropagation();
  305. window.open(`http://${client}`);
  306. }}
  307. >
  308. <Icon name="eye" />
  309. Open
  310. </Menu.Item>
  311. <Menu.Divider />
  312. <Menu.Item
  313. onClick={(e) => {
  314. e.preventDefault();
  315. e.stopPropagation();
  316. download(
  317. `presets.json`,
  318. JSON.stringify(props?.presets, null, 2)
  319. );
  320. }}
  321. >
  322. <Icon name="download" />
  323. Download
  324. </Menu.Item>
  325. <Menu.Item
  326. onClick={(e) => {
  327. e.preventDefault();
  328. e.stopPropagation();
  329. setUpload({ clients: [client] });
  330. }}
  331. >
  332. <Icon name="upload" />
  333. Upload
  334. </Menu.Item>
  335. <Menu.Item
  336. onClick={(e) => {
  337. e.preventDefault();
  338. e.stopPropagation();
  339. onDefaults({ clients: [client] });
  340. }}
  341. >
  342. <Icon name="branch" />
  343. Defaults
  344. </Menu.Item>
  345. <Menu.Item
  346. onClick={(e) => {
  347. e.preventDefault();
  348. e.stopPropagation();
  349. setCopy(props?.presets);
  350. }}
  351. >
  352. <Icon name="inspection" />
  353. Copy
  354. </Menu.Item>
  355. <Menu.Divider />
  356. <Menu.Item
  357. onClick={(e) => {
  358. e.preventDefault();
  359. e.stopPropagation();
  360. setPurge({
  361. clients: [client],
  362. value: Object.keys(props?.presets)
  363. });
  364. }}
  365. >
  366. <Icon name="trash" />
  367. Delete
  368. </Menu.Item>
  369. </StyledMenu>
  370. }
  371. round
  372. >
  373. <Icon name="setting" />
  374. </SplitButton>
  375. </Col>
  376. <Col>
  377. <Slider
  378. defaultValue={from255(current?.brightness)}
  379. onChange={(v) => {
  380. onBrightness({ clients: [client], value: to255(v) });
  381. }}
  382. />
  383. </Col>
  384. </Row>
  385. );
  386. };
  387. const Management = ({
  388. key,
  389. itemKey,
  390. clientName,
  391. onCopy,
  392. onDelete,
  393. onSync,
  394. ...props
  395. }) => {
  396. const [copy, setCopy] = useState(null);
  397. const [purge, setPurge] = useState(null);
  398. const [upload, setUpload] = useState(null);
  399. return (
  400. <Panel
  401. key={key}
  402. itemKey={itemKey}
  403. header={
  404. <>
  405. {clientName} ({props?.client})
  406. </>
  407. }
  408. >
  409. <StateMangement
  410. setPurge={setPurge}
  411. setCopy={setCopy}
  412. setUpload={setUpload}
  413. onSync={onSync}
  414. {...props}
  415. />
  416. <InstanceManagement
  417. {...props}
  418. onDelete={(e) => {
  419. setPurge(e);
  420. }}
  421. onCopy={(e) => {
  422. setCopy(e);
  423. }}
  424. />
  425. <CopyModal
  426. data={copy}
  427. onConfirm={(e) => {
  428. onCopy(e);
  429. setCopy(null);
  430. }}
  431. onCancel={(e) => setCopy(null)}
  432. {...props}
  433. />
  434. <PurgeModal
  435. data={purge}
  436. onConfirm={(e) => {
  437. onDelete(e);
  438. setPurge(null);
  439. }}
  440. onCancel={(e) => setPurge(null)}
  441. {...props}
  442. />
  443. <UploadModal
  444. data={upload}
  445. onConfirm={(e) => {
  446. onCopy(e);
  447. setUpload(null);
  448. }}
  449. onCancel={(e) => setUpload(null)}
  450. {...props}
  451. />
  452. </Panel>
  453. );
  454. };
  455. export default WLED(Management);