Management.js 11 KB

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