handleAction(action.action)}
+ />
+ ))}
+
>
);
diff --git a/src/pages/Devices/Devices.jsx b/src/pages/Devices/Devices.jsx
index de24379f..dc55b1c0 100644
--- a/src/pages/Devices/Devices.jsx
+++ b/src/pages/Devices/Devices.jsx
@@ -19,18 +19,15 @@ const Devices = () => {
const getDisplays = useStore((state) => state.getDisplays);
const displays = useStore((state) => state.displays);
-
-
-
useEffect(() => {
getDisplays();
}, [getDisplays]);
return (
- {displays && Object.keys(displays).map((display, i) => (
+ {displays && Object.keys(displays).length ? Object.keys(displays).map((display, i) => (
- ))}
+ )) : (<>No devices yet>)}
);
};
diff --git a/src/pages/Integrations.jsx b/src/pages/Integrations.jsx
deleted file mode 100644
index c5e9eed3..00000000
--- a/src/pages/Integrations.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { useEffect } from 'react';
-import useStore from '../utils/apiStore';
-
-const Integrations = () => {
- const getIntegrations = useStore((state) => state.getIntegrations);
- const integrations = useStore((state) => state.integrations);
- useEffect(() => {
- getIntegrations();
- }, [getIntegrations]);
- return (
-
-
Integrations
- {integrations
- && Object.keys(integrations).map((s, i) =>
{s}
)}
-
- );
-};
-
-export default Integrations;
diff --git a/src/pages/Integrations/AddIntegration.jsx b/src/pages/Integrations/AddIntegration.jsx
new file mode 100644
index 00000000..c3b1bcc4
--- /dev/null
+++ b/src/pages/Integrations/AddIntegration.jsx
@@ -0,0 +1,199 @@
+import { useState, useEffect } from "react";
+import { makeStyles } from "@material-ui/core/styles";
+
+import Dialog from "@material-ui/core/Dialog";
+import DialogActions from "@material-ui/core/DialogActions";
+import DialogContent from "@material-ui/core/DialogContent";
+import DialogContentText from "@material-ui/core/DialogContentText";
+import DialogTitle from "@material-ui/core/DialogTitle";
+import Select from "@material-ui/core/Select";
+import MenuItem from "@material-ui/core/MenuItem";
+import Button from "@material-ui/core/Button";
+import useStore from "../../utils/apiStore";
+import BladeSchemaFormNew from "../../components/SchemaForm/BladeSchemaFormNew";
+import { Divider } from "@material-ui/core";
+
+const useStyles = makeStyles((theme) => ({
+ wrapper: {
+ minWidth: "200px",
+ padding: "16px 1.2rem 6px 1.2rem",
+ border: "1px solid #999",
+ borderRadius: "10px",
+ position: "relative",
+ margin: "1rem 0",
+ display: "flex",
+ alignItems: "center",
+ "@media (max-width: 580px)": {
+ width: "100%",
+ margin: "0.5rem 0",
+ },
+ "& > label": {
+ top: "-0.7rem",
+ display: "flex",
+ alignItems: "center",
+ left: "1rem",
+ padding: "0 0.3rem",
+ position: "absolute",
+ fontVariant: "all-small-caps",
+ backgroundColor: theme.palette.background.paper,
+ boxSizing: "border-box",
+ },
+ },
+}));
+
+const AddIntegrationDialog = () => {
+ const classes = useStyles();
+
+ const getIntegrations = useStore((state) => state.getIntegrations);
+
+ const addIntegration = useStore((state) => state.addIntegration);
+ const updateIntegration = useStore((state) => state.updateIntegration);
+ const integrations = useStore((state) => state.integrations);
+
+ const open = useStore((state) => state.dialogs.addIntegration?.open || false);
+
+ const integrationId = useStore((state) => state.dialogs.addIntegration?.edit || false);
+ const initial = integrations[integrationId] || { type: "", config: {} };
+
+ const setDialogOpenAddIntegration = useStore(
+ (state) => state.setDialogOpenAddIntegration
+ );
+
+ const integrationsTypes = useStore((state) => state.schemas?.integrations);
+ const showSnackbar = useStore((state) => state.showSnackbar);
+ const [integrationType, setIntegrationType] = useState("");
+ const [model, setModel] = useState({});
+
+ const currentSchema = integrationType ? integrationsTypes[integrationType].schema : {};
+
+ const handleClose = () => {
+ setDialogOpenAddIntegration(false);
+ };
+ const handleAddDevice = (e) => {
+ const cleanedModel = Object.fromEntries(
+ Object.entries(model).filter(([_, v]) => v !== "")
+ );
+ const defaultModel = {};
+
+ for (const key in currentSchema.properties) {
+ currentSchema.properties[key].default !== undefined
+ ? (defaultModel[key] = currentSchema.properties[key].default)
+ : undefined;
+ }
+
+ const valid = currentSchema.required.every((val) =>
+ Object.keys({ ...defaultModel, ...cleanedModel }).includes(val)
+ );
+
+ if (!valid) {
+ showSnackbar({
+ message: "Please fill in all required fields.",
+ messageType: "warning",
+ });
+ } else {
+ if (
+ initial.config &&
+ Object.keys(initial.config).length === 0 &&
+ initial.config.constructor === Object
+ ) {
+ // console.log("ADDING");
+ addIntegration({
+ type: integrationType,
+ config: { ...defaultModel, ...cleanedModel },
+ }).then((res) => {
+ if (res !== "failed") {
+ setDialogOpenAddIntegration(false);
+ getIntegrations();
+ } else {
+ }
+ });
+ } else {
+ // console.log("EDITING");
+ updateIntegration({
+ id: integrationId,
+ type: integrationType,
+ config: { ...model },
+ }).then((res) => {
+ if (res !== "failed") {
+ setDialogOpenAddIntegration(false);
+ getIntegrations();
+ } else {
+ }
+ });
+ }
+ }
+ };
+ const handleTypeChange = (value, initial = {}) => {
+ setIntegrationType(value);
+ setModel(initial);
+ };
+ const handleModelChange = (config) => {
+ setModel({ ...model, ...config });
+ };
+
+ useEffect(() => {
+ handleTypeChange(initial.type, initial.config);
+ }, [initial.type]);
+
+ return (
+
+ );
+};
+
+export default AddIntegrationDialog;
diff --git a/src/pages/Integrations/IntegrationCard.jsx b/src/pages/Integrations/IntegrationCard.jsx
new file mode 100644
index 00000000..a3357db5
--- /dev/null
+++ b/src/pages/Integrations/IntegrationCard.jsx
@@ -0,0 +1,239 @@
+import { useState } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import clsx from 'clsx';
+import Card from '@material-ui/core/Card';
+import Icon from '@material-ui/core/Icon';
+import Button from '@material-ui/core/Button';
+import EditIcon from '@material-ui/icons/Edit';
+import AddIcon from '@material-ui/icons/Add';
+import VisibilityIcon from '@material-ui/icons/Visibility';
+import TuneIcon from '@material-ui/icons/Tune';
+import BuildIcon from '@material-ui/icons/Build';
+import { NavLink } from 'react-router-dom';
+import Wled from '../../assets/Wled';
+import useStore from '../../utils/apiStore';
+import { camelToSnake } from '../../utils/helpers';
+import Popover from '../../components/Popover';
+import EditVirtuals from '../Devices/EditVirtuals';
+import Collapse from '@material-ui/core/Collapse';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import IconButton from '@material-ui/core/IconButton';
+import { CardActions, CardHeader, Switch } from '@material-ui/core';
+
+const useStyles = makeStyles((theme) => ({
+ integrationCardPortrait: {
+ padding: '1rem',
+ margin: '0.5rem',
+ display: 'flex',
+ alignItems: 'flex-start',
+ flexDirection: 'column',
+ width: '290px',
+ justifyContent: 'space-between',
+ '@media (max-width: 580px)': {
+ width: '87vw',
+ height: 'unset',
+ }
+ },
+ integrationLink: {
+ flexGrow: 0,
+ padding: '0 0.5rem',
+ textDecoration: 'none',
+ fontSize: 'large',
+ color: 'inherit',
+
+ '&:hover': {
+ color: theme.palette.primary.main,
+ },
+ },
+ integrationIcon: {
+ margingBottom: '4px',
+ marginRight: '0.5rem',
+ position: 'relative',
+ fontSize: '50px',
+ },
+ integrationCardContainer: {
+ display: 'flex',
+ alignItems: 'center',
+ flexDirection: 'column',
+ width: '100%',
+ height: '100%',
+ justifyContent: 'space-between',
+ '@media (max-width: 580px)': {
+ flexDirection: 'row',
+ },
+ },
+ iconMedia: {
+ height: 140,
+ display: 'flex',
+ alignItems: 'center',
+ margin: '0 auto',
+ fontSize: 100,
+ '& > span:before': {
+ position: 'relative',
+ },
+ },
+ editButton: {
+ minWidth: 32,
+ marginLeft: theme.spacing(1),
+ '@media (max-width: 580px)': {
+ minWidth: 'unset',
+ },
+ },
+ editButtonMobile: {
+ minWidth: 32,
+ marginLeft: theme.spacing(1),
+ '@media (max-width: 580px)': {
+ minWidth: 'unset',
+ flexGrow: 1,
+ },
+ },
+ expand: {
+ display: 'none',
+ transform: 'rotate(0deg)',
+ marginLeft: 'auto',
+ transition: theme.transitions.create('transform', {
+ duration: theme.transitions.duration.shortest,
+ }),
+ '@media (max-width: 580px)': {
+ display: 'block'
+ },
+ },
+ expandOpen: {
+ transform: 'rotate(180deg)',
+ },
+ buttonBar: {
+ '@media (max-width: 580px)': {
+ display: 'none'
+ },
+ },
+ buttonBarMobile: {
+ width: '100%',
+ textAlign: 'right',
+ },
+ buttonBarMobileWrapper: {
+ display: 'flex',
+ margin: '0 -1rem -1rem -1rem',
+ padding: '0.5rem 0.5rem 1.5rem 0.5rem',
+ background: 'rgba(0,0,0,0.4)',
+ '& > div, & > button': {
+ flexGrow: 1,
+ flexBasis: '30%'
+ },
+ '& > div > button': {
+ width: '100%'
+ }
+ },
+}));
+
+const IntegrationCard = ({ integration }) => {
+
+ const classes = useStyles();
+ const getIntegrations = useStore((state) => state.getIntegrations);
+ const integrations = useStore((state) => state.integrations);
+ const deleteIntegration = useStore((state) => state.deleteIntegration);
+ const toggleIntegration = useStore((state) => state.toggleIntegration);
+ const setDialogOpenAddIntegration = useStore((state) => state.setDialogOpenAddIntegration);
+
+ const [expanded, setExpanded] = useState(false);
+ const variant = 'outlined';
+ const color = 'inherit';
+
+ const handleExpandClick = () => {
+ setExpanded(!expanded);
+ };
+
+ const handleDeleteDevice = (integration) => {
+ deleteIntegration(integrations[integration].id).then(() => {
+ getIntegrations();
+ });
+ };
+
+ const handleEditIntegration = (integration) => {
+ setDialogOpenAddIntegration(true, integration)
+ };
+ const handleActivateIntegration = (integration) => {
+ toggleIntegration({
+ id: integration.id
+ }).then(() => getIntegrations())
+ };
+
+ return (
+
+ handleActivateIntegration(integrations[integration])} />
+ }
+ />
+
+
+
+
+
+
+
handleDeleteDevice(integration)}
+ className={classes.editButton}
+ />
+
+
+
+
+
+
+
+
+
+
handleDeleteDevice(integration)}
+ className={classes.editButton}
+ />
+
+
+
+
+
+
+
+
+ )
+}
+
+export default IntegrationCard
diff --git a/src/pages/Integrations/Integrations.jsx b/src/pages/Integrations/Integrations.jsx
new file mode 100644
index 00000000..b7cc161b
--- /dev/null
+++ b/src/pages/Integrations/Integrations.jsx
@@ -0,0 +1,34 @@
+import { useEffect } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import useStore from '../../utils/apiStore';
+import IntegrationCard from './IntegrationCard';
+
+const useStyles = makeStyles((theme) => ({
+ cardWrapper: {
+ display: 'flex', flexWrap: 'wrap', margin: '-0.5rem',
+ },
+ '@media (max-width: 580px)' : {
+ cardWrapper:{
+ justifyContent: 'center'
+ }
+ }
+}));
+
+const Integrations = () => {
+ const classes = useStyles();
+ const getIntegrations = useStore((state) => state.getIntegrations);
+ const integrations = useStore((state) => state.integrations);
+
+ useEffect(() => {
+ getIntegrations();
+ }, [getIntegrations]);
+ return (
+
+ {integrations && Object.keys(integrations).length ? Object.keys(integrations).map((integration, i) => (
+
+ )) : (<>No integrations yet.>)}
+
+ );
+};
+
+export default Integrations;
diff --git a/src/pages/Scenes/Scenes.jsx b/src/pages/Scenes/Scenes.jsx
index d0fc6379..26ecf1c8 100644
--- a/src/pages/Scenes/Scenes.jsx
+++ b/src/pages/Scenes/Scenes.jsx
@@ -93,7 +93,7 @@ const Scenes = () => {
}, [getScenes]);
return (
- {scenes && Object.keys(scenes).map((s, i) => (
+ {scenes && Object.keys(scenes).length ? Object.keys(scenes).map((s, i) => (
handleActivateScene({ id: s })}>
@@ -109,7 +109,7 @@ const Scenes = () => {
- ))}
+ )) : (<>No Scenes yet.>)}
);
};
diff --git a/src/utils/apiStore.jsx b/src/utils/apiStore.jsx
index c5e77b80..02aa6a4f 100644
--- a/src/utils/apiStore.jsx
+++ b/src/utils/apiStore.jsx
@@ -36,6 +36,10 @@ const useStore = create(
open: false,
edit: {},
},
+ addIntegration: {
+ open: false,
+ edit: {},
+ },
},
setDialogOpen: (open, edit = false) => set((state) => ({
dialogs: {
@@ -73,6 +77,15 @@ const useStore = create(
},
},
})),
+ setDialogOpenAddIntegration: (open, edit = false) => set((state) => ({
+ dialogs: {
+ ...state.dialogs,
+ addIntegration: {
+ open,
+ edit,
+ },
+ },
+ })),
ui: {
snackbar: {
isOpen: false,
@@ -147,6 +160,7 @@ const useStore = create(
// set({ dialogs: { nohost: { open: true } } });
}
},
+
displays: {},
getDisplays: async () => {
const resp = await Ledfx('/api/displays', set);
@@ -352,6 +366,7 @@ const useStore = create(
// set({ dialogs: { nohost: { open: true } } });
}
},
+
scenes: {},
getScenes: async () => {
const resp = await Ledfx('/api/scenes', set);
@@ -411,6 +426,7 @@ const useStore = create(
console.log('problems while adding Scene', resp);
}
},
+
integrations: {},
getIntegrations: async () => {
const resp = await Ledfx('/api/integrations', set);
@@ -420,6 +436,67 @@ const useStore = create(
// set({ dialogs: { nohost: { open: true } } });
}
},
+ addIntegration: async (config) => {
+ const resp = await Ledfx(
+ `/api/integrations`,
+ set,
+ 'POST',
+ config,
+ );
+ if (resp) {
+ // set({ presets: resp.preset });
+ console.log(resp);
+ return resp;
+ } else {
+ // set({ dialogs: { nohost: { open: true } } });
+ }
+ },
+ updateIntegration: async (config) => {
+ const resp = await Ledfx(
+ `/api/integrations`,
+ set,
+ 'POST',
+ config,
+ );
+ if (resp) {
+ // set({ presets: resp.preset });
+ console.log(resp);
+ return resp;
+ } else {
+ // set({ dialogs: { nohost: { open: true } } });
+ }
+ },
+ toggleIntegration: async (config) => {
+ const resp = await Ledfx(
+ `/api/integrations`,
+ set,
+ 'PUT',
+ config,
+ );
+ if (resp) {
+ // set({ presets: resp.preset });
+ console.log(resp);
+ return resp;
+ } else {
+ // set({ dialogs: { nohost: { open: true } } });
+ }
+ },
+ deleteIntegration: async (config) => {
+ console.log("YZ", config)
+ const resp = await Ledfx(
+ `/api/integrations`,
+ set,
+ 'DELETE',
+ {data: {id: config}}
+ );
+ if (resp) {
+ // set({ presets: resp.preset });
+ console.log(resp);
+ } else {
+ // set({ dialogs: { nohost: { open: true } } });
+ }
+ },
+
schemas: {},
getSchemas: async () => {
const resp = await Ledfx('/api/schema', set);
@@ -429,6 +506,7 @@ const useStore = create(
// set({ dialogs: { nohost: { open: true } } });
}
},
+
config: {},
getSystemConfig: async () => {
const resp = await Ledfx('/api/config', set);
@@ -488,26 +566,12 @@ const useStore = create(
const resp = await Ledfx('/api/find_devices', set, 'POST', {});
if (resp && resp.status === 'success') {
console.log(resp)
- }
- // if (resp && resp.status === 'success') {
- // set({
- // settings: get().settings,
- // ...{
- // "settings": {
- // audio_inputs: get().settings.audio_inputs,
- // "active_device_index": parseInt(index),
- // }
- // },
-
- // });
- // }
- else {
+ } else {
set({ dialogs: { nohost: { open: true } } });
}
},
-
togglePause: async () => {
const resp = await Ledfx('/api/displays', set, 'PUT', {});
if (resp && resp.data) {