Management.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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. overlay={
  177. <StyledMenu>
  178. <Menu.Item
  179. onClick={(e) => {
  180. download(
  181. `preset_${o}.json`,
  182. JSON.stringify(presets[o], null, 2)
  183. );
  184. }}
  185. >
  186. <Icon name="download" />
  187. Save
  188. </Menu.Item>
  189. <Menu.Item
  190. onClick={(e) => {
  191. onCopy({ [o]: presets[o] });
  192. }}
  193. >
  194. <Icon name="inspection" />
  195. Copy
  196. </Menu.Item>
  197. <Menu.Divider />
  198. <Menu.Item
  199. onClick={(e) => onDelete({ clients: [client], value: [o] })}
  200. >
  201. <Icon name="trash" />
  202. Delete
  203. </Menu.Item>
  204. </StyledMenu>
  205. }
  206. onClick={(e) => {
  207. onPreset({ clients: [client], value: o });
  208. }}
  209. >
  210. <Icon name="checkmark" />
  211. Apply
  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. current,
  227. ...props
  228. }) => {
  229. if (!current) return null;
  230. return (
  231. <Row>
  232. <Col span={12}>
  233. <Button
  234. btnType={current?.on ? "primary" : "outline"}
  235. icon={<Icon name="switch" />}
  236. round
  237. onClick={() =>
  238. onPower({ clients: [client], value: current?.on ? "off" : "on" })
  239. }
  240. >
  241. Lights {current?.on ? "on" : "off"}
  242. </Button>
  243. <PopConfirm
  244. title="Are you sure you want to make enable sync?"
  245. onConfirm={() => onSync({ clients: [client], value: "enable" })}
  246. >
  247. <Button btnType="outline" round disabled={current?.udpSync?.send}>
  248. Enable sync
  249. </Button>
  250. </PopConfirm>
  251. <PopConfirm
  252. title="Are you sure you want to disable sync?"
  253. onConfirm={() => onSync({ clients: [client], value: "disable" })}
  254. >
  255. <Button btnType="outline" round disabled={!current?.udpSync?.send}>
  256. Disable sync
  257. </Button>
  258. </PopConfirm>
  259. <PopConfirm
  260. title="Are you sure you want to reboot?"
  261. onConfirm={() => onPower({ clients: [client], value: "reboot" })}
  262. >
  263. <Button icon={<Icon name="loader" />} btnType="danger" round>
  264. Reboot
  265. </Button>
  266. </PopConfirm>
  267. </Col>
  268. <Col span={12} style={{ textAlign: "right" }}>
  269. <Button.Group round>
  270. <Button
  271. btnType="secondary"
  272. icon={<Icon name="arrow-left" />}
  273. title="Previous preset"
  274. round
  275. onClick={() => {
  276. onPreset({ clients: [client], value: 249 });
  277. }}
  278. />
  279. <Button
  280. btnType="secondary"
  281. icon={<Icon name="arrow-right" />}
  282. title="Next preset"
  283. round
  284. onClick={() => {
  285. onPreset({ clients: [client], value: 250 });
  286. }}
  287. />
  288. </Button.Group>
  289. <SplitButton
  290. onClick={(e) => {
  291. e.preventDefault();
  292. e.stopPropagation();
  293. window.open(`http://${client}`);
  294. }}
  295. overlay={
  296. <StyledMenu>
  297. <Menu.Item
  298. onClick={(e) => {
  299. e.preventDefault();
  300. e.stopPropagation();
  301. window.open(`http://${client}`);
  302. }}
  303. >
  304. <Icon name="eye" />
  305. Open
  306. </Menu.Item>
  307. <Menu.Item
  308. onClick={(e) => {
  309. e.preventDefault();
  310. e.stopPropagation();
  311. download(
  312. `presets.json`,
  313. JSON.stringify(props?.presets, null, 2)
  314. );
  315. }}
  316. >
  317. <Icon name="download" />
  318. Save all
  319. </Menu.Item>
  320. <Menu.Item
  321. onClick={(e) => {
  322. e.preventDefault();
  323. e.stopPropagation();
  324. setUpload({ clients: [client] });
  325. }}
  326. >
  327. <Icon name="upload" />
  328. Restore all
  329. </Menu.Item>
  330. <Menu.Item
  331. onClick={(e) => {
  332. e.preventDefault();
  333. e.stopPropagation();
  334. setCopy(props?.presets);
  335. }}
  336. >
  337. <Icon name="inspection" />
  338. Copy all
  339. </Menu.Item>
  340. <Menu.Divider />
  341. <Menu.Item
  342. onClick={(e) => {
  343. e.preventDefault();
  344. e.stopPropagation();
  345. setPurge({
  346. clients: [client],
  347. value: Object.keys(props?.presets),
  348. });
  349. }}
  350. >
  351. <Icon name="trash" />
  352. Delete all
  353. </Menu.Item>
  354. </StyledMenu>
  355. }
  356. round
  357. >
  358. <Icon name="setting" />
  359. </SplitButton>
  360. </Col>
  361. <Col>
  362. <Slider
  363. defaultValue={from255(current?.brightness)}
  364. onChange={(v) => {
  365. onBrightness({ clients: [client], value: to255(v) });
  366. }}
  367. />
  368. </Col>
  369. </Row>
  370. );
  371. };
  372. const Management = ({
  373. key,
  374. itemKey,
  375. clientName,
  376. onCopy,
  377. onDelete,
  378. onSync,
  379. ...props
  380. }) => {
  381. const [copy, setCopy] = useState(null);
  382. const [purge, setPurge] = useState(null);
  383. const [upload, setUpload] = useState(null);
  384. return (
  385. <Panel
  386. key={key}
  387. itemKey={itemKey}
  388. header={
  389. <>
  390. {clientName} ({props?.client})
  391. </>
  392. }
  393. >
  394. <StateMangement
  395. setPurge={setPurge}
  396. setCopy={setCopy}
  397. setUpload={setUpload}
  398. onSync={onSync}
  399. {...props}
  400. />
  401. <InstanceManagement
  402. {...props}
  403. onDelete={(e) => {
  404. setPurge(e);
  405. }}
  406. onCopy={(e) => {
  407. setCopy(e);
  408. }}
  409. />
  410. <CopyModal
  411. data={copy}
  412. onConfirm={(e) => {
  413. onCopy(e);
  414. setCopy(null);
  415. }}
  416. onCancel={(e) => setCopy(null)}
  417. {...props}
  418. />
  419. <PurgeModal
  420. data={purge}
  421. onConfirm={(e) => {
  422. onDelete(e);
  423. setPurge(null);
  424. }}
  425. onCancel={(e) => setPurge(null)}
  426. {...props}
  427. />
  428. <UploadModal
  429. data={upload}
  430. onConfirm={(e) => {
  431. onCopy(e);
  432. setUpload(null);
  433. }}
  434. onCancel={(e) => setUpload(null)}
  435. {...props}
  436. />
  437. </Panel>
  438. );
  439. };
  440. export default WLED(Management);