| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- import { useEffect, useRef, useState } from "react";
- import styled from "styled-components";
- import {
- Button,
- Col,
- Collapse,
- Icon,
- Menu,
- Modal,
- PopConfirm,
- Row,
- ScrollIndicator,
- Slider,
- SplitButton,
- Transfer,
- } from "tiny-ui";
- import WLED from "./WLED.js";
- const { Panel } = Collapse;
- const to255 = (v) => Math.ceil((v / 100) * 255);
- const from255 = (v) => Math.ceil((v / 255) * 100);
- const download = (filename, data, type = "application/json") => {
- const blob = new Blob([data], { type: type });
- const href = URL.createObjectURL(blob);
- const link = document.createElement("a");
- link.href = href;
- link.download = filename;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- };
- const StyledRow = styled((props) => <Row {...props} />)`
- margin: 0.25rem 0.5rem;
- & .action {
- text-align: right;
- & .ty-icon {
- margin-right: 1rem;
- }
- }
- `;
- const StyledMenu = styled((props) => <Menu {...props} />)`
- & .ty-icon {
- margin-right: 1rem;
- }
- `;
- const ScrollingDiv = (props) => {
- const targetRef = useRef(null);
- return (
- <>
- <ScrollIndicator fixed={false} target={() => targetRef?.current} />
- <div
- ref={targetRef}
- style={{ overflowY: "scroll", height: 200 }}
- {...props}
- />
- </>
- );
- };
- const UploadModal = ({ data, client, onConfirm, ...rest }) => {
- const [payload, setPayload] = useState({});
- useEffect(() => {
- if (data) setPayload({ ...data });
- }, [data]);
- const handleConfirm = (_) => {
- onConfirm(payload);
- };
- const handleChange = (e) => {
- const fileReader = new FileReader();
- fileReader.readAsText(e?.target?.files?.[0], "UTF-8");
- fileReader.onload = (e) => {
- if (e?.target?.result) {
- setPayload({ ...payload, value: JSON.parse(e?.target?.result) });
- }
- };
- };
- if (!data) return null;
- return (
- <Modal
- header="Upload preset"
- visible={true}
- onConfirm={handleConfirm}
- {...rest}
- >
- <h5>Are you sure you want to upload and override presets?</h5>
- {payload?.value ? (
- <ScrollingDiv>
- <pre>{JSON.stringify(payload?.value, null, 2)}</pre>
- </ScrollingDiv>
- ) : null}
- <input type="file" onChange={handleChange} />
- </Modal>
- );
- };
- const PurgeModal = ({ data, client, onConfirm, ...rest }) => {
- const [payload, setPayload] = useState({});
- useEffect(() => {
- if (data) setPayload({ ...data });
- }, [data]);
- const handleConfirm = (_) => {
- onConfirm(payload);
- };
- if (!data) return null;
- return (
- <Modal
- header="Delete preset"
- visible={true}
- onConfirm={handleConfirm}
- {...rest}
- >
- <h5>Are you sure you want to delete these presets?</h5>
- <ScrollingDiv>
- <pre>{JSON.stringify(data?.value, null, 2)}</pre>
- </ScrollingDiv>
- </Modal>
- );
- };
- const CopyModal = ({ data, client, clients, onConfirm, ...rest }) => {
- const [transfer, setTransfer] = useState({});
- const [payload, setPayload] = useState({});
- useEffect(() => {
- setPayload({ ...payload, value: data });
- }, [data]);
- useEffect(() => {
- if (clients)
- setTransfer(
- clients
- ?.filter((o) => o !== client)
- ?.map((o, i) => ({ key: o, label: o }))
- );
- }, [clients]);
- const handleConfirm = (_) => {
- onConfirm(payload);
- };
- if (!data) return null;
- return (
- <Modal
- header="Copy preset"
- visible={true}
- onConfirm={handleConfirm}
- {...rest}
- >
- <ScrollingDiv>
- <pre>{JSON.stringify(data, null, 2)}</pre>
- </ScrollingDiv>
- <Row>
- <Col>
- <Transfer
- dataSource={transfer}
- defaultValue={[]}
- onChange={(value) => {
- setPayload({ ...payload, clients: value });
- }}
- />
- </Col>
- </Row>
- </Modal>
- );
- };
- const InstanceManagement = ({
- client,
- presets,
- onCopy,
- onPreset,
- onDelete,
- }) => {
- if (!presets) return null;
- return Object.keys(presets)?.map((o, i) => (
- <StyledRow key={i}>
- <Col span={16}>{presets[o]?.name || ""}</Col>
- <Col span={8} className="action">
- <SplitButton
- btnType="primary"
- overlay={
- <StyledMenu>
- <Menu.Item
- onClick={(e) => {
- download(
- `preset_${o}.json`,
- JSON.stringify(presets[o], null, 2)
- );
- }}
- >
- <Icon name="download" />
- Save
- </Menu.Item>
- <Menu.Item
- onClick={(e) => {
- onCopy({ [o]: presets[o] });
- }}
- >
- <Icon name="inspection" />
- Copy
- </Menu.Item>
- <Menu.Divider />
- <Menu.Item
- onClick={(e) => onDelete({ clients: [client], value: [o] })}
- >
- <Icon name="trash" />
- Delete
- </Menu.Item>
- </StyledMenu>
- }
- onClick={(e) => {
- onPreset({ clients: [client], value: o });
- }}
- >
- <Icon name="checkmark" />
- Apply
- </SplitButton>
- </Col>
- </StyledRow>
- ));
- };
- const StateMangement = ({
- client,
- setPurge,
- setUpload,
- setCopy,
- onPower,
- onSync,
- onBrightness,
- onPreset,
- current,
- ...props
- }) => {
- if (!current) return null;
- return (
- <Row>
- <Col span={12}>
- <Button
- btnType={current?.on ? "primary" : "outline"}
- icon={<Icon name="switch" />}
- round
- onClick={() =>
- onPower({ clients: [client], value: current?.on ? "off" : "on" })
- }
- >
- Lights {current?.on ? "on" : "off"}
- </Button>
- <PopConfirm
- title="Are you sure you want to make enable sync?"
- onConfirm={() => onSync({ clients: [client], value: "enable" })}
- >
- <Button btnType="outline" round disabled={current?.udpSync?.send}>
- Enable sync
- </Button>
- </PopConfirm>
- <PopConfirm
- title="Are you sure you want to disable sync?"
- onConfirm={() => onSync({ clients: [client], value: "disable" })}
- >
- <Button btnType="outline" round disabled={!current?.udpSync?.send}>
- Disable sync
- </Button>
- </PopConfirm>
- <PopConfirm
- title="Are you sure you want to reboot?"
- onConfirm={() => onPower({ clients: [client], value: "reboot" })}
- >
- <Button icon={<Icon name="loader" />} btnType="danger" round>
- Reboot
- </Button>
- </PopConfirm>
- </Col>
- <Col span={12} style={{ textAlign: "right" }}>
- <Button.Group round>
- <Button
- btnType="secondary"
- icon={<Icon name="arrow-left" />}
- title="Previous preset"
- round
- onClick={() => {
- onPreset({ clients: [client], value: 249 });
- }}
- />
- <Button
- btnType="secondary"
- icon={<Icon name="arrow-right" />}
- title="Next preset"
- round
- onClick={() => {
- onPreset({ clients: [client], value: 250 });
- }}
- />
- </Button.Group>
- <SplitButton
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- window.open(`http://${client}`);
- }}
- overlay={
- <StyledMenu>
- <Menu.Item
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- window.open(`http://${client}`);
- }}
- >
- <Icon name="eye" />
- Open
- </Menu.Item>
- <Menu.Item
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- download(
- `presets.json`,
- JSON.stringify(props?.presets, null, 2)
- );
- }}
- >
- <Icon name="download" />
- Save all
- </Menu.Item>
- <Menu.Item
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- setUpload({ clients: [props?.client] });
- }}
- >
- <Icon name="upload" />
- Restore all
- </Menu.Item>
- <Menu.Item
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- setCopy(props?.presets);
- }}
- >
- <Icon name="inspection" />
- Copy all
- </Menu.Item>
- <Menu.Divider />
- <Menu.Item
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- setPurge({
- clients: [props?.client],
- value: Object.keys(props?.presets),
- });
- }}
- >
- <Icon name="trash" />
- Delete all
- </Menu.Item>
- </StyledMenu>
- }
- round
- >
- <Icon name="setting" />
- </SplitButton>
- </Col>
- <Col>
- <Slider
- defaultValue={from255(current?.brightness)}
- onChange={(v) => {
- onBrightness({ clients: [client], value: to255(v) });
- }}
- />
- </Col>
- </Row>
- );
- };
- const Management = ({
- key,
- itemKey,
- clientName,
- onCopy,
- onDelete,
- onSync,
- ...props
- }) => {
- const [copy, setCopy] = useState(null);
- const [purge, setPurge] = useState(null);
- const [upload, setUpload] = useState(null);
- return (
- <Panel
- key={key}
- itemKey={itemKey}
- header={
- <>
- {clientName} ({props?.client})
- </>
- }
- >
- <StateMangement
- setPurge={setPurge}
- setCopy={setCopy}
- setUpload={setUpload}
- onSync={onSync}
- {...props}
- />
- <InstanceManagement
- {...props}
- onDelete={(e) => {
- setPurge(e);
- }}
- onCopy={(e) => {
- setCopy(e);
- }}
- />
- <CopyModal
- data={copy}
- onConfirm={(e) => {
- onCopy(e);
- setCopy(null);
- }}
- onCancel={(e) => setCopy(null)}
- {...props}
- />
- <PurgeModal
- data={purge}
- onConfirm={(e) => {
- onDelete(e);
- setPurge(null);
- }}
- onCancel={(e) => setPurge(null)}
- {...props}
- />
- <UploadModal
- data={upload}
- onConfirm={(e) => {
- onCopy(e);
- setUpload(null);
- }}
- onCancel={(e) => setUpload(null)}
- {...props}
- />
- </Panel>
- );
- };
- export default WLED(Management);
|