Merge branch 'staging_environment'

This commit is contained in:
Carter 2025-05-26 09:54:15 -06:00
commit d2fac1f335
33 changed files with 1915 additions and 116 deletions

View file

@ -72,14 +72,14 @@ const useStyles = makeStyles((theme: Theme) => {
marginLeft: theme.spacing(0.05),
marginRight: theme.spacing(0.05),
"&:hover": {
backgroundColor: getSecondaryColour(), //use the colour of the whitelabel
//backgroundColor: "gold",
//backgroundColor: getSecondaryColour(), //use the colour of the whitelabel
backgroundColor: "gold", //for now Dustin just wants it to be gold
color: "black",
}
},
buttonToggled: {
backgroundColor: getSecondaryColour(), //use the colour of the whitelabel
//backgroundColor: "gold",
//backgroundColor: getSecondaryColour(), //use the colour of the whitelabel
backgroundColor: "gold", //for now Dustin just wants it to be gold
color: "black",
borderRadius: 24,
fontWeight: "bold"
@ -148,9 +148,9 @@ export default function ButtonGroup(props: Props){
}}>
{b.icon
?
b.icon
b.icon
:
<Typography sx={{fontSize: textSize}}>
<Typography sx={{fontSize: textSize ?? 13, fontWeight: checkToggled(i) ? 650 : 500}}>
{b.title}
</Typography>
}

View file

@ -1,4 +1,4 @@
import { Box, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
import { Box, Button, Card, Checkbox, CircularProgress, Collapse, darken, Divider, Grid2, IconButton, InputAdornment, ListItemButton, ListItemIcon, ListItemText, Menu, MenuItem, Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { ArrowDownward, ArrowUpward, ChevronRight, Close, Search, ViewColumn } from "@mui/icons-material"
import { useEffect, useState } from "react";
@ -98,6 +98,8 @@ interface Props<T> {
setOrderBy?: React.Dispatch<React.SetStateAction<string>>;
order?: string;
setOrder?: React.Dispatch<React.SetStateAction<"asc" | "desc">>;
endTitleElement?: string | JSX.Element | (() => string | JSX.Element);
loadMore?: () => void
}
export default function ResponsiveTable<T extends Object>(props: Props<T>) {
@ -130,13 +132,15 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
orderBy,
setOrderBy,
order,
setOrder
setOrder,
loadMore
} = props
const classes = useStyles();
const columns = typeof(props.columns) === "function" ? props.columns() : props.columns
const subtitle = typeof(props.subtitle) === "function" ? props.subtitle() : props.subtitle
const title = typeof(props.title) === "function" ? props.title() : props.title
// const order = props.order ? props.order : "asc"
const endTitleElement = typeof(props.endTitleElement) === "function" ? props.endTitleElement() : props.endTitleElement
const isMobile = useMobile()
@ -371,6 +375,17 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
</Box>
)
}
{(loadMore && rows.length < total) &&
<Box>
<Button
onClick={loadMore}
color="primary"
sx={{width: "100%"}}
variant="contained">
Load More
</Button>
</Box>
}
</Box>
)
@ -387,8 +402,20 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
const columnClick = (column: Column<T>) => {
const sortKey = column.sortKey ? column.sortKey : column.title.toLowerCase()
// console.log(column.sortKey)
if (setOrderBy) setOrderBy(sortKey)
if (setOrder && order) setOrder(order === "asc" ? "desc" : "asc")
if (setOrderBy) {
if (orderBy === sortKey && setOrder && order) {
if (order === "desc") {
setOrder("asc")
} else {
setOrderBy("")
}
// setOrder(order === "asc" ? "desc" : "asc")
} else {
if (setOrder) setOrder("desc")
setOrderBy(sortKey)
}
}
// if (setOrder && order) setOrder(order === "asc" ? "desc" : "asc")
}
const showOrderIcon = (column: Column<T>) => {
@ -422,6 +449,9 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
<Grid2>
{actions && actions}
</Grid2>
<Grid2>
{endTitleElement}
</Grid2>
<Grid2>
{setSearchText &&<Box className={classes.searchContainer}>
{searchBar()}

149
src/common/StatusDust.tsx Normal file
View file

@ -0,0 +1,149 @@
import { Box, Grid2, Theme, Tooltip, Typography } from "@mui/material";
import { green } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import { Device } from "models";
import PulseBox from "./PulseBox";
import { or } from "utils";
import { useEffect, useState } from "react";
interface Props {
device: Device
}
const useStyles = makeStyles((theme: Theme) => {
const lightMode = theme.palette?.mode === "light";
return ({
box: {
display: "inline-block",
borderRadius: "6px",
backgroundColor: lightMode ? "rgb(210, 210, 210)" : "rgb(50, 50, 50)",
padding: theme.spacing(0.75),
marginRight: "auto",
margin: theme.spacing(1),
width: theme.spacing(16),
height: theme.spacing(11),
},
carouselContainer: {
position: 'relative',
height: '40px',
overflow: 'hidden',
textAlign: "center",
},
carouselItem: {
opacity: 0,
transform: 'translateX(100%)',
transition: 'opacity 0.5s ease, transform 0.5s ease',
position: 'absolute',
width: '100%',
overflow: "hidden",
// height: '100%',
},
active: {
opacity: 1,
transform: 'translateX(0)',
},
inactive: {
transform: 'translateX(-100%)',
},
})
})
export default function StatusDust(props: Props) {
const { device } = props;
const classes = useStyles()
const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []);
const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []);
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
const [, setColorIndex] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setColorIndex(prevIndex => {
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
setColor(colors[newIndex]); // Use the new index here
return newIndex;
});
}, 7500);
return () => clearInterval(interval);
}, []);
// const [currentIndex, setCurrentIndex] = useState(0);
// Auto-cycle through measurements every 3 seconds
// useEffect(() => {
// const interval = setInterval(() => {
// setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
// }, 3000); // Adjust timing as needed (3000ms = 3s)
// return () => clearInterval(interval); // Cleanup on unmount
// }, []);
const tooltip = () => {
if (messages.length === 0) return null
if (messages.length < 2) return (
messages[0]
)
return (
<Grid2 container direction={"column"}>
<Grid2 container direction="row">
<Typography fontWeight={"bold"} variant="caption">
Overlay Messages
</Typography>
</Grid2>
{messages.map((message, index) => {
return (
<Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignItems={"center"}>
<Box
sx={{
width: 10, // Size of the square
height: 10,
backgroundColor: colors[index], // Passed color prop
borderRadius: 1, // Optional: slight rounding, remove for sharp square
border: "1px solid black",
}}
/>
<Typography variant="caption" sx={{ marginLeft: 1 }}>
{message}
</Typography>
</Grid2>
)
})}
</Grid2>
)
}
return (
<Tooltip title={tooltip()}>
<PulseBox color={color} className={classes.box} >
<Grid2 container direction="column" >
<Grid2 container direction="column"
className={`${classes.carouselItem} ${classes.active }`}
>
<Grid2>
<Typography variant="body2" style={{ color: green[300]}}>
{"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[400]}}>
{"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[500]}}>
{"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[600]}}>
{"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
</Grid2>
</Grid2>
</PulseBox>
</Tooltip>
)
}

198
src/common/StatusGas.tsx Normal file
View file

@ -0,0 +1,198 @@
import { Box, Grid2, Theme, Tooltip, Typography } from "@mui/material";
import { blue, deepOrange, teal } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import { Device } from "models";
import PulseBox from "./PulseBox";
import { or } from "utils";
import { useEffect, useState } from "react";
interface Props {
device: Device
// noDust?: boolean;
gasType: "co2" | "co" | "no2" | "o2" | "all"
}
const useStyles = makeStyles((theme: Theme) => {
const lightMode = theme.palette?.mode === "light";
return ({
box: {
display: "inline-block",
borderRadius: "6px",
backgroundColor: lightMode ? "rgb(210, 210, 210)" : "rgb(50, 50, 50)",
padding: theme.spacing(0.75),
marginRight: "auto",
margin: theme.spacing(1),
width: theme.spacing(14),
height: theme.spacing(6),
},
boxTall: {
display: "inline-block",
borderRadius: "6px",
backgroundColor: lightMode ? "rgb(210, 210, 210)" : "rgb(50, 50, 50)",
padding: theme.spacing(0.75),
marginRight: "auto",
margin: theme.spacing(1),
width: theme.spacing(14),
height: theme.spacing(8.5),
},
carouselContainer: {
position: 'relative',
height: '40px',
overflow: 'hidden',
textAlign: "center",
},
carouselItem: {
opacity: 0,
transform: 'translateX(100%)',
transition: 'opacity 0.5s ease, transform 0.5s ease',
position: 'absolute',
width: '100%',
overflow: "hidden",
// height: '100%',
},
active: {
opacity: 1,
transform: 'translateX(0)',
},
inactive: {
transform: 'translateX(-100%)',
},
})
})
export default function StatusGas(props: Props) {
const { device, gasType } = props;
const classes = useStyles()
// console.log(noDust)
let colors: string[] = []
let messages: string[] = []
if (gasType === "all") {
colors = or(device.status.o2?.overlays.map(overlay => overlay.color), []);
colors.push(...or(device.status.co2?.overlays.map(overlay => overlay.color), []));
colors.push(...or(device.status.no2?.overlays.map(overlay => overlay.color), []));
colors.push(...or(device.status.co?.overlays.map(overlay => overlay.color), []));
messages = or(device.status.o2?.overlays.map(overlay => overlay.message), []);
messages.push(...or(device.status.co2?.overlays.map(overlay => overlay.message), []));
messages.push(...or(device.status.no2?.overlays.map(overlay => overlay.message), []));
messages.push(...or(device.status.co?.overlays.map(overlay => overlay.message), []));
} else {
colors = or(device.status[gasType]?.overlays.map(overlay => overlay.color), []);
messages = or(device.status[gasType]?.overlays.map(overlay => overlay.message), []);
}
const [color, setColor] = useState(colors.length > 0 ? colors[0] : "");
const [, setColorIndex] = useState(0)
const gasTypes: ("o2" | "no2" | "co2" | "co")[] = ["o2", "no2", "co2", "co"]
useEffect(() => {
const interval = setInterval(() => {
setColorIndex(prevIndex => {
const newIndex = prevIndex >= colors.length - 1 ? 0 : prevIndex + 1;
setColor(colors[newIndex]); // Use the new index here
return newIndex;
});
}, 7500);
return () => clearInterval(interval);
}, []);
const [currentIndex, setCurrentIndex] = useState(0);
// Auto-cycle through measurements every 5 seconds
useEffect(() => {
if (gasType !== "all") {
setCurrentIndex(0)
return
}
const interval = setInterval(() => {
if (gasType === "all") setCurrentIndex((prevIndex) => (prevIndex + 1) % 4);
else setCurrentIndex((prevIndex) => (prevIndex + 1) % 4);
}, 6000);
return () => clearInterval(interval); // Cleanup on unmount
}, [gasType]);
const tooltip = () => {
if (messages.length === 0) return null
if (messages.length < 2) return (
messages[0]
)
return (
<Grid2 container direction={"column"}>
<Grid2 container direction="row">
<Typography fontWeight={"bold"} variant="caption">
Overlay Messages
</Typography>
</Grid2>
{messages.map((message, index) => {
return (
<Grid2 key={"tooltip-overly-message-" + message} container direction="row" alignContent={"center"}>
<Grid2 size={{xs: 1}} alignContent={"center"}>
<Box
sx={{
width: 12, // Size of the square
height: 12,
backgroundColor: colors[index], // Passed color prop
borderRadius: 3, // Optional: slight rounding, remove for sharp square
border: "1px solid black",
marginBottom: 0.4
}}
/>
</Grid2>
<Grid2 size={{ xs: 11 }} alignContent={"center"}>
<Typography variant="caption">
{message}
</Typography>
</Grid2>
</Grid2>
)
})}
</Grid2>
)
}
const gasDisplay = () => {
const gas = gasType === "all" ? gasTypes[currentIndex] : gasType
// if (gasType !== "all") {
return (
<Tooltip title={tooltip()}>
<PulseBox color={color} className={gasType === "all" ? classes.boxTall : classes.box}>
<Grid2 container direction="column" >
{ gasType === "all" &&
<Typography variant="body2">
{gas.toUpperCase()}
</Typography>
}
<Grid2 container direction="column">
<Grid2>
{gas !== "o2" ?
<Typography variant="body2" style={{ color: teal[500]}}>
Gas: {device.status[gas]?.ppm.toFixed(1)}ppm
</Typography>
:
<Typography variant="body2" style={{ color: blue[500]}}>
Gas: {(device.status[gas]?.ppm!/10000).toFixed(1)}%
</Typography>
}
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: deepOrange[800]}}>
Volt: {device.status[gas]?.millivolts.toFixed(1)}mV
</Typography>
</Grid2>
</Grid2>
</Grid2>
</PulseBox>
</Tooltip>
)
// }
}
return (
<>
{gasDisplay()}
</>
)
}

View file

@ -5,8 +5,10 @@ import { Device } from "models";
import PulseBox from "./PulseBox";
import { or } from "utils";
import { useEffect, useState } from "react";
interface Props {
device: Device
noDust?: boolean;
}
const useStyles = makeStyles((theme: Theme) => {
@ -19,8 +21,8 @@ const useStyles = makeStyles((theme: Theme) => {
padding: theme.spacing(0.75),
marginRight: "auto",
margin: theme.spacing(1),
width: "72px",
height: "50px",
width: theme.spacing(16),
height: theme.spacing(11),
},
carouselContainer: {
position: 'relative',
@ -47,10 +49,10 @@ const useStyles = makeStyles((theme: Theme) => {
})
})
export default function StatusPlenum(props: Props) {
const { device } = props;
export default function StatusSen5x(props: Props) {
const { device, noDust } = props;
const classes = useStyles()
// console.log(noDust)
const colors = or(device.status.sen5x?.overlays.map(overlay => overlay.color), []);
const messages = or(device.status.sen5x?.overlays.map(overlay => overlay.message), []);
@ -71,14 +73,18 @@ export default function StatusPlenum(props: Props) {
const [currentIndex, setCurrentIndex] = useState(0);
// Auto-cycle through measurements every 3 seconds
// Auto-cycle through measurements every 5 seconds
useEffect(() => {
if (noDust) {
setCurrentIndex(0)
return
}
const interval = setInterval(() => {
setCurrentIndex((prevIndex) => (prevIndex + 1) % 4);
}, 3000); // Adjust timing as needed (3000ms = 3s)
if (!noDust) setCurrentIndex((prevIndex) => (prevIndex + 1) % 2);
}, 5000);
return () => clearInterval(interval); // Cleanup on unmount
}, []);
}, [noDust]);
const tooltip = () => {
if (messages.length === 0) return null
@ -125,66 +131,52 @@ export default function StatusPlenum(props: Props) {
>
<Grid2>
<Typography variant="body2" style={{ color: orange[700]}}>
{device.status.sen5x?.temperature.toFixed(1)}°C
Temp: {device.status.sen5x?.temperature.toFixed(1)}°C
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: blue[700]}}>
{device.status.sen5x?.humidity.toFixed(1)}%
Humidity: {device.status.sen5x?.humidity.toFixed(1)}%
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: blue[300]}}>
Voc: {device.status.sen5x?.voc.toFixed(1)}%
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: blue[400]}}>
Nox: {device.status.sen5x?.nox.toFixed(1)}%
</Typography>
</Grid2>
</Grid2>
<Grid2 container direction="column"
{!noDust && <Grid2 container direction="column"
className={`${classes.carouselItem} ${
1 === currentIndex ? classes.active : classes.inactive
}`}
>
<Grid2>
<Typography variant="body2" style={{ color: green[300]}}>
{device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
{"<1um:"} {device.status.sen5x?.dust1ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[400]}}>
{device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
{"<2.5um:"} {device.status.sen5x?.dust2ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
</Grid2>
<Grid2 container direction="column"
className={`${classes.carouselItem} ${
2 === currentIndex ? classes.active : classes.inactive
}`}
>
<Grid2>
<Typography variant="body2" style={{ color: green[500]}}>
{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
{"<4um: "}{device.status.sen5x?.dust4ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: green[600]}}>
{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
{"<10um: "}{device.status.sen5x?.dust10ug.toFixed(1)}ug/m3
</Typography>
</Grid2>
</Grid2>
<Grid2 container direction="column"
className={`${classes.carouselItem} ${
3 === currentIndex ? classes.active : classes.inactive
}`}
>
<Grid2>
<Typography variant="body2" style={{ color: blue[300]}}>
V: {device.status.sen5x?.voc.toFixed(1)}%
</Typography>
</Grid2>
<Grid2>
<Typography variant="body2" style={{ color: blue[400]}}>
N: {device.status.sen5x?.nox.toFixed(1)}%
</Typography>
</Grid2>
</Grid2>
</Grid2>}
</Grid2>
</PulseBox>
</Tooltip>

View file

@ -17,7 +17,7 @@ import { isController, isSource } from "pbHelpers/ComponentType";
import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
import { pond } from "protobuf-ts/pond";
import { useGlobalState } from "providers";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { or } from "utils/types";
import ComponentSettings from "./ComponentSettings";
import { green, teal } from "@mui/material/colors";
@ -94,6 +94,7 @@ export default function ComponentActions(props: Props) {
};
const openComponentSettingsDialog = (mode: string) => {
console.log("delete")
setIsComponentSettingsOpen(true);
setComponentSettingsMode(or(mode, ""));
};
@ -140,7 +141,7 @@ export default function ComponentActions(props: Props) {
{canEdit && (
<MenuItem dense onClick={() => {
openComponentSettingsDialog("remove")
closeMoreMenu()
setAnchorEl(null);
}}>
<ListItemIcon>
<DeleteOutline className={classes.red} />

View file

@ -254,15 +254,15 @@ export default function ComponentCard(props: Props) {
size="small"
onTouchStart={event => {
event.stopPropagation();
event.preventDefault();
//event.preventDefault();
}}
onMouseDown={event => {
event.stopPropagation();
event.preventDefault();
//event.preventDefault();
}}
onClick={event => {
event.stopPropagation();
event.preventDefault();
//event.preventDefault();
}}
disabled={!canEdit}
onChange={(_, checked) => updateControllerState(checked)}

View file

@ -4,7 +4,7 @@ import {
Check,
PermScanWifi,
} from "@mui/icons-material";
import { Card, Grid2, Skeleton, Theme, Typography, useTheme } from "@mui/material";
import { Card, Grid2, Theme, Typography } from "@mui/material";
import { green } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import classNames from "classnames";
@ -41,7 +41,7 @@ interface Props {
export default function DevicesSummary(props: Props) {
const { setFieldContains, group } = props;
const deviceAPI = useDeviceAPI()
const theme = useTheme()
// const theme = useTheme()
const classes = useStyles()
const [stats, setStats] = useState<pond.DeviceStatistics>(pond.DeviceStatistics.create())
@ -205,7 +205,7 @@ export default function DevicesSummary(props: Props) {
<Grid2>
<CircleGraphIcon
sx={{ marginRight: 1, marginLeft: 1 }}
progress={!loadingStats ? getPercent(stats.warning) : undefined}
progress={!loadingStats ? getPercent(stats.offline) : undefined}
color={green[50]}
icon={<PermScanWifi />}
/>

View file

@ -5204,6 +5204,933 @@ export default function DeviceSVG(props: Props) {
);
};
const streamlineMonitorSVG = () => {
return (
<svg id="streamlineMonitor" version="1.1" viewBox="0 0 224.2 224.2">
<g>
<rect
className={classes.boxBorder}
x="4.5"
y=".5"
width="214.5"
height="222.6"
rx="10.7"
ry="10.7"
/>
<path
className={classes.grayLine}
d="M216,122.9l-21.6-13.4c-2.5-1.6-5.7-1.6-8.3,0l-21.6,13.4c-2.3,1.4-3.7,3.9-3.7,6.7v6l-4.5-2.8c-2.3-1.4-5.1-1.4-7.3,0l-18.9,11.7c-2.1,1.3-3.3,3.5-3.3,5.9v6l-4.8-3c-2.1-1.3-4.6-1.3-6.7,0l-17,10.5c-1.9,1.2-3,3.2-3,5.4v54.3h5.1v-2.5c0-1.3,1-2.3,2.3-2.3h11.1c1.3,0,2.3,1,2.3,2.3v2.5h5.1v-2.5c0-1.3,1-2.3,2.3-2.3h3.3v4.8h5.5v-4.8h2.3c1.3,0,2.3,1,2.3,2.3v2.5h5.1v-18.3h5.3c1.5,0,2.6,1.2,2.6,2.6v15.6h5.5v-15.6c0-1.5,1.2-2.6,2.6-2.6h2.8v18.3h6.1v-18.3h3.5c1.5,0,2.6,1.2,2.6,2.6v15.6h5.5v-31.4h5.6c1.7,0,3.1,1.4,3.1,3.1v28.3h6.1v-28.3c0-1.7,1.4-3.1,3.1-3.1h14.1c1.7,0,3.1,1.4,3.1,3.1v26.9l2.2-1.5.4-.3c.2-.2.4-.4.6-.7v-.2c.6-.7,1.1-1.5,1.5-2.2.4-.8.8-1.5,1.3-2.1v-.2c.1,0,.1-85.4.1-85.4,0-2.7-1.4-5.2-3.7-6.7ZM123.3,217.4c-2.1,0-3.8,1.7-3.8,3.8v1.1h-2.2v-1.1c0-2.1-1.7-3.8-3.8-3.8h-11.1c-2.1,0-3.8,1.7-3.8,3.8v1.1h-2.2v-52.8c0-1.7.9-3.2,2.3-4.1l17-10.5c1.6-1,3.5-1,5.1,0l5.6,3.5v59.2h-3.3ZM140.4,222.2h-2.2v-1.1c0-2.1-1.7-3.8-3.8-3.8h-2.3v-9.3c0-1.5,1.2-2.6,2.6-2.6h5.6v16.8ZM158,203.9c-2.3,0-4.1,1.8-4.1,4.1v14.1h-2.6v-14.1c0-2.3-1.8-4.1-4.1-4.1h-12.3c-2.3,0-4.1,1.8-4.1,4.1v14.1h-2.6v-71.7c0-1.9,1-3.7,2.6-4.7l18.9-11.7c1.8-1.1,4-1.1,5.8,0l5.3,3.3v66.6h-2.8ZM177,222.2h-2.6v-14.1c0-2.3-1.8-4.1-4.1-4.1h-3.5v-8.6c0-1.7,1.4-3.1,3.1-3.1h7v30ZM218.2,214.6c-.5.7-.9,1.4-1.3,2.1-.4.7-.8,1.4-1.3,2.1h-.1c-.1.3-.3.5-.5.6h0c0,0,0-24,0-24,0-2.5-2.1-4.6-4.6-4.6h-14.1c-2.5,0-4.6,2.1-4.6,4.6v26.8h-3.1v-26.8c0-2.5-2.1-4.6-4.6-4.6h-14.1c-2.5,0-4.6,2.1-4.6,4.6v26.8h-3.1v-92.6c0-2.2,1.1-4.3,3-5.4l21.6-13.4c2.1-1.3,4.6-1.3,6.7,0l21.6,13.4c1.9,1.2,3,3.2,3,5.4v85Z"
/>
<g>
<path
className={classes.whiteLine}
d="M63.3,202.8l-.8.8c.7.7,1,1.5,1,2.5s-.4,1.8-1,2.5c-1.4,1.4-3.6,1.4-4.9,0-1.4-1.4-1.4-3.6,0-4.9l-.8-.8c-1.8,1.8-1.8,4.8,0,6.6.9.9,2.1,1.4,3.3,1.4s2.4-.5,3.3-1.4c.9-.9,1.4-2,1.4-3.3s-.5-2.4-1.4-3.3Z"
/>
<rect className={classes.whiteLine} x="59.4" y="201.2" width="1.2" height="5.5" />
</g>
<g>
<g>
<path
className={classes.whiteLine}
d="M17.8,77.5l3.1-.6v6.5h-1.5v-4.9l-1.6.4v-1.3Z"
/>
<path
className={classes.whiteLine}
d="M20.2,116.3c.2-.2.3-.4.4-.5.1-.2.2-.3.3-.5,0-.1,0-.3,0-.5s0-.2,0-.3c0,0,0-.2-.2-.3s-.2-.1-.3-.2c-.1,0-.2,0-.4,0s-.4,0-.5.1c-.1,0-.3.2-.3.4,0,.2-.1.4-.1.6h-1.5c0-.5,0-.9.3-1.2.2-.4.5-.7.9-.9.4-.2.8-.3,1.4-.3s.7,0,1,.2c.3.1.6.3.8.5.2.2.4.4.5.7s.2.5.2.7c0,.3,0,.7-.3,1s-.4.6-.7.8l-1.5,1.3h2.6v1.4h-5.4l2.8-2.9Z"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M20.7,38.1c-3.5,0-6.3,2.8-6.3,6.3s2.8,6.3,6.3,6.3,6.3-2.8,6.3-6.3-2.8-6.3-6.3-6.3ZM20.7,48.3c-2.2,0-4-1.8-4-4s1.8-4,4-4,4,1.8,4,4-1.8,4-4,4Z"
/>
<circle className={classes.whiteLine} cx="20.7" cy="44.3" r="2.3" />
</g>
</g>
<g>
<path
className={classes.whiteLine}
d="M188,153.2c0-2.8-1.1-5.3-3-7.3-.8-.8-2-.8-2.8,0-.8.8-.8,2,0,2.8,1.2,1.2,1.9,2.8,1.9,4.5s-.7,3.3-1.9,4.5c-.4.4-.6.9-.6,1.4s.2,1,.6,1.4c.8.8,2,.8,2.8,0,1.9-1.9,3-4.5,3-7.3ZM194.4,153.2c0-4.5-1.7-8.7-4.9-11.8-.8-.8-2-.8-2.8,0-.8.8-.8,2,0,2.8,2.4,2.4,3.8,5.6,3.8,9.1s-1.3,6.6-3.8,9.1c-.4.4-.6.9-.6,1.4s.2,1,.6,1.4c.8.8,2,.8,2.8,0,3.2-3.2,4.9-7.4,4.9-11.8ZM194.1,169.6c4.4-4.4,6.8-10.2,6.8-16.4s-2.4-12-6.8-16.4c-.8-.8-2-.8-2.8,0-.4.4-.6.9-.6,1.4s.2,1,.6,1.4c3.6,3.6,5.6,8.5,5.6,13.6s-2,10-5.6,13.6c-.8.8-.8,2,0,2.8.8.8,2,.8,2.8,0Z"
/>
<path
className={classes.whiteLine}
d="M179.5,148.8c-.6-.6-1.8-.6-2.4,0-.7.7-.7,1.7,0,2.4.5.5.8,1.2.8,2s-.3,1.5-.8,2c-.3.3-.5.8-.5,1.2s.2.9.5,1.2c.3.3.8.5,1.2.5s.9-.2,1.2-.5c1.2-1.2,1.8-2.7,1.8-4.4s-.6-3.2-1.8-4.4Z"
/>
</g>
<g>
<g>
<g>
<path
className={classes.whiteLine}
d="M128.5,103.6v.9c0,.4.3.8.7.8v.9h.2v-.9c.4,0,.7-.4.7-.8v-.9l-.8.7-.8-.7ZM129.9,104.6c0,.3-.2.6-.5.6v-.3h-.2v.3c-.3,0-.5-.3-.5-.6v-.5l.6.5.6-.5v.5Z"
/>
<path
className={classes.whiteLine}
d="M128.5,102.2v.9c0,.5.4.8.8.8s.8-.4.8-.8v-.9l-.8.7-.8-.7ZM129.9,103.1c0,.3-.2.6-.5.6v-.3h-.2v.3c-.3,0-.5-.3-.5-.6v-.5l.6.5.6-.5v.5Z"
/>
<path
className={classes.whiteLine}
d="M128.5,100.7v.9c0,.5.4.8.8.8s.8-.4.8-.8v-.9l-.8.7-.8-.7ZM129.9,101.7c0,.3-.2.6-.5.6v-.3h-.2v.3c-.3,0-.5-.3-.5-.6v-.5l.6.5.6-.5v.5Z"
/>
<path
className={classes.whiteLine}
d="M129.3,101.1h0s0,0,0,0c0,0,.4,0,.5-.3.1-.3,0-.6-.4-1.1h0c0,0,0,0,0,0-.4.5-.5.8-.4,1.1.1.3.5.3.5.3ZM129.3,99.9c.3.4.4.7.3.8,0,.1-.1.2-.2.2v-.3h-.2v.3c0,0-.2,0-.2-.2,0-.1,0-.4.3-.8Z"
/>
<path
className={classes.whiteLine}
d="M144,103.4h-2.5v1c-.2.1-.5.2-.8.2-.9,0-1.7-.8-1.7-1.7s.8-1.7,1.7-1.7,1.7.7,1.7,1.6h1.6c0-1.8-1.5-3.3-3.3-3.3s-2.8,1-3.2,2.5c-.4-1.4-1.7-2.5-3.2-2.5s-3.3,1.5-3.3,3.3,1.5,3.3,3.3,3.3,1.2-.2,1.7-.5v.5h1.6v-2.1c.5,1.2,1.7,2.1,3.1,2.1s1.2-.2,1.7-.5v.5h1.6v-2.9h0ZM134.2,104.6c-.9,0-1.7-.8-1.7-1.7s.8-1.7,1.7-1.7,1.7.8,1.7,1.7-.8,1.7-1.7,1.7Z"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M146.9,102.6l-.3-.6h-.9l-.3.6h-.4l1.2-2.5,1.1,2.5h-.4ZM145.9,101.7h.7l-.2-.5s0,0,0-.1c0,0,0,0,0-.2,0,0,0,.1,0,.1,0,0,0,0,0,.1l-.2.5Z"
/>
<path
className={classes.whiteLine}
d="M148.1,102.6v-2.4h.5c.3,0,.6,0,.7,0,.1,0,.3,0,.4.2.1.1.2.2.3.4,0,.2.1.4.1.6s0,.4-.1.6c0,.2-.2.3-.3.4-.1,0-.2.1-.4.2-.1,0-.3,0-.6,0h-.6ZM148.5,102.3h.3c.2,0,.3,0,.4,0,0,0,.2,0,.2-.1,0,0,.2-.2.2-.3,0-.1,0-.2,0-.4s0-.3,0-.4c0-.1-.1-.2-.2-.3,0,0-.1,0-.2-.1,0,0-.2,0-.4,0h-.3v1.7Z"
/>
<path
className={classes.whiteLine}
d="M152.6,102.6l-.3-.6h-.9l-.3.6h-.4l1.2-2.5,1.1,2.5h-.4ZM151.5,101.7h.7l-.2-.5s0,0,0-.1c0,0,0,0,0-.2,0,0,0,.1,0,.1,0,0,0,0,0,.1l-.2.5Z"
/>
<path
className={classes.whiteLine}
d="M154.1,101.6v1.1h-.4v-2.4h.6c.2,0,.3,0,.4,0,0,0,.2,0,.2,0,0,0,.1.1.2.2,0,0,0,.2,0,.3s0,.2,0,.3c0,0-.1.2-.2.2,0,0-.1,0-.2,0,0,0-.2,0-.4,0h-.2ZM154.1,101.3h0c.2,0,.4,0,.4,0s.1-.1.1-.2,0-.2-.1-.3c0,0-.2,0-.4,0h0v.7Z"
/>
<path className={classes.whiteLine} d="M156.8,100.6v2h-.4v-2h-.6v-.3h1.6v.3h-.6Z" />
<path className={classes.whiteLine} d="M158.3,102.6v-2.4h.4v2.4h-.4Z" />
<path
className={classes.whiteLine}
d="M160.5,102.7l-1.1-2.5h.4l.5,1.3c0,0,0,.1,0,.2,0,0,0,.1,0,.2,0,0,0-.1,0-.2,0,0,0-.1,0-.2l.5-1.3h.4l-1.1,2.5Z"
/>
<path
className={classes.whiteLine}
d="M162.3,102.6v-2.4h1.3v.3h-.9v.6h.9v.3h-.9v.8h.9v.3h-1.3Z"
/>
<path
className={classes.whiteLine}
d="M146.9,106.2l-.3-.6h-.9l-.3.6h-.4l1.2-2.5,1.1,2.5h-.4ZM145.9,105.2h.7l-.2-.5s0,0,0-.1c0,0,0,0,0-.2,0,0,0,.1,0,.1,0,0,0,0,0,.1l-.2.5Z"
/>
<path
className={classes.whiteLine}
d="M149.3,105h.9s0,0,0,0c0,0,0,.1,0,.2,0,.3,0,.6-.3.8s-.5.3-.8.3-.4,0-.5,0c-.2,0-.3-.1-.4-.3-.1-.1-.2-.2-.3-.4,0-.2,0-.3,0-.5s0-.3,0-.5c0-.2.2-.3.3-.4.1-.1.3-.2.4-.3.2,0,.3,0,.5,0s.4,0,.5.1c.1,0,.3.2.4.3l-.3.2c0,0-.2-.2-.3-.2-.1,0-.2,0-.3,0-.3,0-.5,0-.6.2-.2.2-.2.4-.2.6s0,.5.2.7c.2.2.4.3.6.3s.4,0,.5-.2c.1-.1.2-.2.2-.4h0s-.6,0-.6,0v-.3Z"
/>
<path
className={classes.whiteLine}
d="M151.6,105.1v1.1h-.4v-2.4h.5c.2,0,.3,0,.4,0,0,0,.2,0,.2.1,0,0,.1.1.2.2,0,0,0,.2,0,.3,0,.2,0,.3-.1.4,0,.1-.2.2-.4.2l.8,1.1h-.4l-.8-1.1h0ZM151.6,104.8h0c.2,0,.3,0,.4,0,0,0,.1-.1.1-.3s0-.2-.1-.3c0,0-.2,0-.4,0h0v.7Z"
/>
<path className={classes.whiteLine} d="M153.6,106.2v-2.4h.4v2.4h-.4Z" />
<path
className={classes.whiteLine}
d="M157,104.4c-.1,0-.2-.2-.3-.2-.1,0-.2,0-.4,0-.3,0-.5,0-.6.2-.2.2-.2.4-.2.6s0,.5.2.6c.2.2.4.2.6.2s.3,0,.4,0c.1,0,.2-.1.4-.2v.4c-.1,0-.2.1-.3.2-.1,0-.3,0-.4,0s-.3,0-.5,0-.3-.1-.4-.3c-.1-.1-.2-.2-.3-.4,0-.2,0-.3,0-.5s0-.3,0-.5c0-.2.2-.3.3-.4.1-.1.3-.2.4-.3.2,0,.3,0,.5,0s.3,0,.4,0,.2.1.4.2v.4Z"
/>
<path
className={classes.whiteLine}
d="M158,103.8h.4v1.4c0,.2,0,.4.1.5,0,.1.2.2.4.2s.3,0,.4-.2c0-.1.1-.3.1-.5v-1.4h.4v1.5c0,.3,0,.6-.2.7-.2.2-.4.2-.7.2s-.6,0-.7-.2c-.2-.2-.2-.4-.2-.7v-1.5Z"
/>
<path className={classes.whiteLine} d="M161,106.2v-2.4h.4v2h.9v.3h-1.2Z" />
<path className={classes.whiteLine} d="M163.7,104.1v2h-.4v-2h-.6v-.3h1.6v.3h-.6Z" />
<path
className={classes.whiteLine}
d="M165.1,103.8h.4v1.4c0,.2,0,.4.1.5,0,.1.2.2.4.2s.3,0,.4-.2c0-.1.1-.3.1-.5v-1.4h.4v1.5c0,.3,0,.6-.2.7s-.4.2-.7.2-.6,0-.7-.2c-.2-.2-.2-.4-.2-.7v-1.5Z"
/>
<path
className={classes.whiteLine}
d="M168.5,105.1v1.1h-.4v-2.4h.5c.2,0,.3,0,.4,0,0,0,.2,0,.2.1,0,0,.1.1.2.2,0,0,0,.2,0,.3,0,.2,0,.3-.1.4,0,.1-.2.2-.4.2l.8,1.1h-.4l-.8-1.1h0ZM168.5,104.8h0c.2,0,.3,0,.4,0,0,0,.1-.1.1-.3s0-.2-.1-.3c0,0-.2,0-.4,0h0v.7Z"
/>
<path
className={classes.whiteLine}
d="M170.5,106.2v-2.4h1.3v.3h-.9v.6h.9v.3h-.9v.8h.9v.3h-1.3Z"
/>
</g>
</g>
<g>
<path
className={classes.whiteLine}
d="M100.4,99.1v3h-.6v-3h.6ZM99.9,100.3h2.2v.6h-2.2v-.6ZM102.4,99.1v3h-.6v-3h.6Z"
/>
<path
className={classes.whiteLine}
d="M103.4,101.7s0,0,.1,0c0,0,.1,0,.2,0s.2,0,.2,0c0,0,.1,0,.2-.2,0,0,0-.2,0-.2v.3c0,.1,0,.2,0,.3s-.2.2-.3.2c-.1,0-.2,0-.3,0s-.2,0-.3,0c-.1,0-.2-.1-.3-.2,0,0,0-.2,0-.3,0-.2,0-.3.2-.5.1-.1.3-.2.6-.2s.2,0,.3,0c0,0,.2,0,.2,0,0,0,.1,0,.1.1v.3c0,0-.2-.1-.3-.1,0,0-.2,0-.3,0s-.2,0-.2,0c0,0,0,0-.1,0,0,0,0,0,0,.1s0,.1,0,.1ZM103,100.4c.1,0,.2-.1.4-.2s.3,0,.5,0,.3,0,.5,0c.1,0,.2.1.3.2,0,0,.1.2.1.3v1.4h-.5v-1.3c0,0,0-.1,0-.1,0,0,0,0,0,0,0,0,0,0-.1,0,0,0-.1,0-.2,0,0,0-.2,0-.3,0,0,0-.2,0-.2,0,0,0-.1,0-.1,0l-.2-.4Z"
/>
<path
className={classes.whiteLine}
d="M105.8,102.2h-.6v-2h.6v2ZM106.3,100.7s0,0-.1,0-.1,0-.2,0c0,0,0,0-.1.2,0,0,0,.2,0,.2l-.2-.2c0-.2,0-.3,0-.4,0-.1.1-.2.2-.3,0,0,.2-.1.3-.1s.1,0,.2,0c0,0,.1,0,.2.1l-.3.5s0,0-.1,0Z"
/>
<path
className={classes.whiteLine}
d="M107,100.6c0-.2.2-.3.4-.4.1,0,.3-.1.5-.1s.3,0,.4.1c.1,0,.2.2.3.4s.1.3.1.6,0,.4-.1.6-.2.3-.3.4c-.1,0-.3.1-.4.1s-.3,0-.5-.1c-.1,0-.3-.2-.4-.4,0-.2-.1-.3-.1-.6s0-.4.1-.6ZM107.5,101.5c0,0,.1.2.2.2,0,0,.2,0,.3,0s.2,0,.2,0c0,0,.1-.1.2-.2,0,0,0-.2,0-.3s0-.2,0-.3c0,0-.1-.2-.2-.2,0,0-.2,0-.2,0s-.2,0-.3,0c0,0-.2.1-.2.2,0,0,0,.2,0,.3s0,.2,0,.3ZM109,98.8v3.4h-.6v-3.4h.6Z"
/>
<path
className={classes.whiteLine}
d="M109.9,100.2l.4,1.1.6-1.3.6,1.3.4-1.1h.6l-1,2.1-.6-1.3-.6,1.3-1-2.1h.6Z"
/>
<path
className={classes.whiteLine}
d="M113.2,101.7s0,0,.1,0c0,0,.1,0,.2,0s.2,0,.2,0c0,0,.1,0,.2-.2,0,0,0-.2,0-.2v.3c0,.1,0,.2,0,.3s-.2.2-.3.2c-.1,0-.2,0-.3,0s-.2,0-.3,0c-.1,0-.2-.1-.3-.2,0,0,0-.2,0-.3,0-.2,0-.3.2-.5.1-.1.3-.2.6-.2s.2,0,.3,0c0,0,.2,0,.2,0,0,0,.1,0,.1.1v.3c0,0-.2-.1-.3-.1,0,0-.2,0-.3,0s-.2,0-.2,0c0,0,0,0-.1,0,0,0,0,0,0,.1s0,.1,0,.1ZM112.7,100.4c.1,0,.2-.1.4-.2s.3,0,.5,0,.3,0,.5,0c.1,0,.2.1.3.2,0,0,.1.2.1.3v1.4h-.5v-1.3c0,0,0-.1,0-.1,0,0,0,0,0,0,0,0,0,0-.1,0,0,0-.1,0-.2,0,0,0-.2,0-.3,0,0,0-.2,0-.2,0,0,0-.1,0-.1,0l-.2-.4Z"
/>
<path
className={classes.whiteLine}
d="M115.6,102.2h-.6v-2h.6v2ZM116,100.7s0,0-.1,0-.1,0-.2,0c0,0,0,0-.1.2,0,0,0,.2,0,.2l-.2-.2c0-.2,0-.3,0-.4,0-.1.1-.2.2-.3,0,0,.2-.1.3-.1s.1,0,.2,0c0,0,.1,0,.2.1l-.3.5s0,0-.1,0Z"
/>
<path
className={classes.whiteLine}
d="M117,102.1c-.2,0-.3-.2-.4-.4,0-.2-.1-.3-.1-.5s0-.4.1-.6c0-.2.2-.3.4-.4.2,0,.4-.1.6-.1s.4,0,.6.1.3.2.4.3.1.3.1.5,0,0,0,0c0,0,0,0,0,0h-1.8v-.3h1.3l-.2.2s0,0,0,0c0,0,0,0,0,0,0,0,0-.2,0-.3,0,0,0-.1-.2-.2,0,0-.1,0-.2,0s-.2,0-.3,0c0,0-.1.1-.2.2,0,0,0,.2,0,.3,0,.1,0,.2,0,.3,0,0,0,.2.2.2,0,0,.2,0,.3,0s.2,0,.3,0,.2-.1.2-.2l.5.2c-.1.2-.2.3-.4.4-.2,0-.4.1-.6.1s-.4,0-.6-.1Z"
/>
<path
className={classes.whiteLine}
d="M120.9,102.2h-.6v-3.4h.6v3.4ZM122.4,101.7c0,.2-.2.3-.4.4s-.3.1-.5.1-.3,0-.4-.1c-.1,0-.2-.2-.3-.4s-.1-.3-.1-.6,0-.4.1-.6.2-.3.3-.4c.1,0,.3-.1.4-.1s.3,0,.5.1.3.2.4.4c0,.2.1.3.1.6s0,.4-.1.6ZM121.9,100.9c0,0-.1-.2-.2-.2s-.2,0-.3,0-.2,0-.2,0c0,0-.1.1-.2.2s0,.2,0,.3,0,.2,0,.3.1.2.2.2c0,0,.2,0,.2,0s.2,0,.3,0,.1-.1.2-.2c0,0,0-.2,0-.3s0-.2,0-.3Z"
/>
<path
className={classes.whiteLine}
d="M123.5,103.1h-.6l.5-1.2-.8-1.8h.6l.6,1.6h-.3s.6-1.6.6-1.6h.6l-1.3,3Z"
/>
<path
className={classes.whiteLine}
d="M125.2,100c0,0,.2-.1.3-.1s.2,0,.3.1c0,0,.1.2.1.2s0,.2-.1.2c0,0-.2.1-.3.1s-.2,0-.3-.1c0,0-.1-.2-.1-.2s0-.2.1-.2ZM125.2,101.7c0,0,.2-.1.3-.1s.2,0,.3.1c0,0,.1.2.1.3s0,.2-.1.2c0,0-.2.1-.3.1s-.2,0-.3-.1c0,0-.1-.2-.1-.2s0-.2.1-.3Z"
/>
</g>
<rect className={classes.whiteLine} x="79.3" y="95.3" width="92.3" height=".8" />
<polygon
className={classes.whiteLine}
points="90.1 92 90.1 93.8 92.6 95.7 90.1 97.7 90.1 99.4 94.7 95.7 90.1 92"
/>
<polygon
className={classes.whiteLine}
points="85.6 92 85.6 93.8 88.1 95.7 85.6 97.7 85.6 99.4 90.3 95.7 85.6 92"
/>
<path
className={classes.whiteLine}
d="M62.7,103.1c-.2,0-.5,0-.7,0-.2,0-.3-.1-.4-.3s0-.3,0-.5h0c.5-1,1.2-1.8,2.2-2.3,1-.5,2-.7,3.1-.6.2,0,.3.1.4.3s0,.3,0,.5c-.5,1-1.2,1.8-2.2,2.3-.7.4-1.6.6-2.4.6ZM62.1,102.6h0ZM62.2,102.5c.9,0,1.7,0,2.5-.5.8-.4,1.4-1,1.8-1.8-.9,0-1.7,0-2.5.5-.8.4-1.4,1-1.8,1.8Z"
/>
<path
className={classes.whiteLine}
d="M67.7,97.1s0,0-.1,0c-1.1,0-2.1-.4-3-1-.1-.1-.2-.3-.2-.4s0-.3.2-.4h0c.9-.6,1.9-.9,3-.9,1.1,0,2.1.4,3,1,.1.1.2.3.2.4,0,.2,0,.3-.2.4-.9.6-1.9.9-2.9.9ZM65.1,95.6c.7.5,1.6.8,2.5.8.9,0,1.7-.2,2.5-.7-.7-.5-1.6-.8-2.5-.8-.9,0-1.7.2-2.5.7Z"
/>
<path
className={classes.whiteLine}
d="M79.1,95.5c-1,0-1.9-.3-2.7-.8-.9-.6-1.6-1.4-2-2.4s0-.3,0-.5c0-.1.2-.2.4-.2h0c1.1,0,2.1.2,3,.8.9.6,1.6,1.4,2,2.4,0,.2,0,.3,0,.5,0,.1-.2.2-.4.2-.1,0-.2,0-.3,0ZM75.1,92.2c.4.8.9,1.5,1.7,2,.7.5,1.6.7,2.5.7-.4-.8-.9-1.5-1.7-2-.7-.5-1.6-.7-2.5-.7ZM74.9,92.2h0Z"
/>
<path
className={classes.whiteLine}
d="M75,99.9c-.1,0-.2,0-.3,0-.2,0-.3-.1-.4-.2,0-.1-.1-.3,0-.5.4-1,1.1-1.8,2-2.4.9-.6,2-.9,3-.8.2,0,.3.1.4.2s.1.3,0,.5c-.4,1-1.1,1.8-2,2.4-.8.5-1.8.8-2.7.8ZM74.9,99.3c.9,0,1.7-.2,2.5-.7.7-.5,1.3-1.2,1.7-2-.9,0-1.7.2-2.5.7-.7.5-1.3,1.2-1.7,2Z"
/>
<path
className={classes.whiteLine}
d="M74.1,95.5c-1,0-1.9-.3-2.7-.8-.9-.6-1.6-1.4-2-2.4,0-.2,0-.3,0-.5,0-.1.2-.2.4-.2h0c1.1,0,2.1.2,3,.8.9.6,1.6,1.4,2,2.4,0,.2,0,.3,0,.5,0,.1-.2.2-.4.2-.1,0-.2,0-.3,0ZM70,92.2c.4.8.9,1.5,1.7,2,.7.5,1.6.7,2.5.7-.4-.8-.9-1.5-1.7-2s-1.6-.7-2.5-.7ZM69.8,92.2h0Z"
/>
<path
className={classes.whiteLine}
d="M70,99.9c-.1,0-.2,0-.3,0-.2,0-.3-.1-.4-.2,0-.1-.1-.3,0-.5.4-1,1.1-1.8,2-2.4.9-.6,2-.9,3-.8.2,0,.3.1.4.2,0,.1.1.3,0,.5-.4,1-1.1,1.8-2,2.4-.8.5-1.8.8-2.7.8ZM69.9,99.3c.9,0,1.7-.2,2.5-.7.7-.5,1.3-1.2,1.7-2-.9,0-1.7.2-2.5.7-.7.5-1.3,1.2-1.7,2ZM74.2,96.6h0,0ZM74.1,96.5h0s0,0,0,0Z"
/>
<path
className={classes.whiteLine}
d="M84.2,95.5c-1,0-1.9-.3-2.7-.8-.9-.6-1.6-1.4-2-2.4,0-.2,0-.3,0-.5,0-.1.2-.2.4-.2h0c1.1,0,2.1.2,3,.8.9.6,1.6,1.4,2,2.4,0,.2,0,.3,0,.5,0,.1-.2.2-.4.2-.1,0-.2,0-.3,0ZM80.2,92.2c.4.8.9,1.5,1.7,2,.7.5,1.6.7,2.5.7-.4-.8-.9-1.5-1.7-2s-1.6-.7-2.5-.7ZM80,92.2h0Z"
/>
<path
className={classes.whiteLine}
d="M80.1,99.9c-.1,0-.2,0-.3,0-.2,0-.3-.1-.4-.2,0-.1-.1-.3,0-.5.4-1,1.1-1.8,2-2.4.9-.6,2-.9,3-.8.2,0,.3.1.4.2,0,.1.1.3,0,.5-.4,1-1.1,1.8-2,2.4-.8.5-1.8.8-2.7.8ZM80,99.3c.9,0,1.7-.2,2.5-.7.7-.5,1.3-1.2,1.7-2-.9,0-1.7.2-2.5.7-.7.5-1.3,1.2-1.7,2ZM84.4,96.6h0,0ZM84.2,96.5h0s0,0,0,0Z"
/>
<path
className={classes.whiteLine}
d="M60.8,97.8c-.7,0-1.4-.1-2-.4-1-.4-1.8-1.1-2.4-2.1,0-.1,0-.3,0-.5s.2-.3.4-.3c1.1-.2,2.1-.1,3.1.3,1,.4,1.8,1.1,2.4,2.1,0,.1,0,.3,0,.5,0,.2-.2.3-.4.3-.4,0-.8.1-1.1.1ZM57.1,95.2c.5.7,1.2,1.3,2,1.7.8.3,1.7.4,2.6.3-.5-.7-1.2-1.3-2-1.7-.8-.3-1.7-.4-2.6-.3ZM56.9,95.2h0,0Z"
/>
<g>
<path
className={classes.whiteLine}
d="M64.8,86.5c0,2.1-1.7,3.3-4.2,3.3s-3.4-.6-4.3-1.2l1-2c.9.5,2.1,1,3.3,1s1.6-.2,1.6-.8c0-1.5-5.4-.5-5.5-4.1,0-2,1.6-3.3,4.2-3.3s2.5.4,3.5.9l-.9,2c-.9-.4-2-.7-2.7-.7s-1.4.4-1.4.8c0,1.6,5.6.3,5.6,4.1Z"
/>
<path
className={classes.whiteLine}
d="M76.7,81.8h-3v7.8h-2.6v-7.8h-3v-2.3h8.5v2.3Z"
/>
<path
className={classes.whiteLine}
d="M85.2,86.7h-2v2.9h-2.6v-10.1h5.4c2.4,0,3.8,1.5,3.8,3.5s-.7,2.6-1.9,3.3l2.5,3.3h-3.1l-2-2.9ZM83.1,84.4h2.2c1.3,0,1.7-.6,1.7-1.4s-.5-1.3-1.6-1.3h-2.3v2.7Z"
/>
<path
className={classes.whiteLine}
d="M96,83.4h5.1v2.2h-5.1v1.7h5.6v2.3h-8.2v-10.1h8.1v2.3h-5.6v1.6Z"
/>
<path
className={classes.whiteLine}
d="M111.5,88.1h-4.6l-.6,1.5h-2.8l4.5-10.1h2.5l4.5,10.1h-2.8l-.6-1.5ZM110.6,85.9l-1.2-2.9-.2-.7-.2.7-1.2,2.9h2.9Z"
/>
<path
className={classes.whiteLine}
d="M129.2,89.6h-2.6v-6l-.2.3-2.4,2.9h-.6l-2.4-2.9-.2-.3v6h-2.6v-10.1h1.9l3.4,3.8.3.4.3-.4,3.4-3.8h1.9v10.1Z"
/>
<path className={classes.whiteLine} d="M140.5,89.6h-7.4v-10.1h2.6v7.8h4.8v2.3Z" />
<path className={classes.whiteLine} d="M146.3,89.6h-2.6v-10.1h2.6v10.1Z" />
<path
className={classes.whiteLine}
d="M160.5,89.6h-1.9l-4.9-5.9-.2-.4v6.3h-2.6v-10.1h2.3l4.6,5.5.2.4v-5.9h2.6v10.1Z"
/>
<path
className={classes.whiteLine}
d="M166.9,83.4h5.1v2.2h-5.1v1.7h5.6v2.3h-8.2v-10.1h8.1v2.3h-5.6v1.6Z"
/>
</g>
</g>
</g>
<g>
<g>
<path
className={classes.whiteLine}
d="M16,186.5h10.7c.4,0,.6.3.6.6v6.6c0,.8-.7,1.5-1.5,1.5h-9c-.8,0-1.5-.7-1.5-1.5v-6.6c0-.4.3-.6.6-.6Z"
/>
<path
className={classes.boxBorder}
d="M17.8,186.9v-1.6c0-1.9,1.6-3.5,3.5-3.5h0c1.9,0,3.5,1.6,3.5,3.5v1.6"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M16,149.9h10.7c.4,0,.6.3.6.6v6.6c0,.8-.7,1.5-1.5,1.5h-9c-.8,0-1.5-.7-1.5-1.5v-6.6c0-.4.3-.6.6-.6Z"
/>
<path
className={classes.boxBorder}
d="M17.8,150.4v-1.6c0-1.9,1.6-3.5,3.5-3.5h0c1.9,0,3.5,1.6,3.5,3.5v1.6"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M93,203.8h10.7c.4,0,.6.3.6.6v6.6c0,.8-.7,1.5-1.5,1.5h-9c-.8,0-1.5-.7-1.5-1.5v-6.6c0-.4.3-.6.6-.6Z"
/>
<path
className={classes.boxBorder}
d="M94.8,204.2v-1.6c0-1.9,1.6-3.5,3.5-3.5h0c1.9,0,3.5,1.6,3.5,3.5v1.6"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M134.8,203.8h10.7c.4,0,.6.3.6.6v6.6c0,.8-.7,1.5-1.5,1.5h-9c-.8,0-1.5-.7-1.5-1.5v-6.6c0-.4.3-.6.6-.6Z"
/>
<path
className={classes.boxBorder}
d="M136.7,204.2v-1.6c0-1.9,1.6-3.5,3.5-3.5h0c1.9,0,3.5,1.6,3.5,3.5v1.6"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M176.7,203.8h10.7c.4,0,.6.3.6.6v6.6c0,.8-.7,1.5-1.5,1.5h-9c-.8,0-1.5-.7-1.5-1.5v-6.6c0-.4.3-.6.6-.6Z"
/>
<path
className={classes.boxBorder}
d="M178.5,204.2v-1.6c0-1.9,1.6-3.5,3.5-3.5h0c1.9,0,3.5,1.6,3.5,3.5v1.6"
/>
</g>
</g>
{/* buttons for port selection */}
{/* I2C PORTS */}
<ellipse
onClick={e => {
openMenu(e, {
address: 0, //address here is not important for I2C, the restrictions are per component not per port
addressType: quack.AddressType.ADDRESS_TYPE_I2C,
label: "1",
autoDetectable: false
});
}}
className={classes.testButton}
id="button"
cx="20.5"
cy="44"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* CONFIGURABLE PIN PORTS - Note input Ports 3 and 4 and all control ports are locked for the monitor */}
{/* PORT 1 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 1,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "1",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="20"
cy="80"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* PORT 2 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 2,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "2",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="20.5"
cy="116"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* PORT 3 */}
<ellipse
onClick={e => {
setLockOpen(true);
// openMenu(e, {
// address: 4,
// addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
// label: "3",
// autoDetectable: true
// });
}}
className={classes.testButton}
id="button"
cx="21.5"
cy="153"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* Port 4 */}
<ellipse
onClick={e => {
setLockOpen(true);
// openMenu(e, {
// address: 8,
// addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
// label: "3",
// autoDetectable: true
// });
}}
className={classes.testButton}
id="button"
cx="21.5"
cy="189"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* Controller ports - note these are all locked for monitor devices */}
{/* PORT C1 */}
<ellipse
onClick={e => {
setLockOpen(true);
// openMenu(e, {
// address: 2,
// addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
// label: "2",
// autoDetectable: true
// });
}}
className={classes.testButton}
id="button"
cx="98"
cy="206"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* PORT C2 */}
<ellipse
onClick={e => {
setLockOpen(true);
// openMenu(e, {
// address: 4,
// addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
// label: "3",
// autoDetectable: true
// });
}}
className={classes.testButton}
id="button"
cx="140"
cy="206"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* Port C3 */}
<ellipse
onClick={e => {
setLockOpen(true);
// openMenu(e, {
// address: 8,
// addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
// label: "3",
// autoDetectable: true
// });
}}
className={classes.testButton}
id="button"
cx="182"
cy="206"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
</svg>
);
};
const streamlineAutomateSVG = () => {
return (
<svg id="StreamlineAutomate" version="1.1" viewBox="0 0 216 224.09">
<rect
className={classes.boxBorder}
x=".74"
y=".74"
width="214.53"
height="222.62"
rx="10.71"
ry="10.71"
/>
<path
className={classes.grayLine}
d="M212.22,123.15l-21.63-13.37c-2.55-1.57-5.71-1.57-8.25,0l-21.63,13.37c-2.33,1.44-3.72,3.94-3.72,6.68v6.01l-4.52-2.79c-2.26-1.4-5.06-1.4-7.32,0l-18.95,11.71c-2.07,1.28-3.3,3.49-3.3,5.93v5.96l-4.85-3c-2.05-1.27-4.61-1.27-6.66,0l-17.04,10.53c-1.88,1.16-3.01,3.18-3.01,5.39v54.29h5.11v-2.53c0-1.27,1.03-2.3,2.31-2.3h11.1c1.27,0,2.3,1.03,2.3,2.3v2.53h5.11v-2.53c0-1.27,1.03-2.3,2.3-2.3h3.32v4.83h5.52v-4.83h2.27c1.27,0,2.3,1.03,2.3,2.3v2.53h5.11v-18.25h5.31c1.46,0,2.64,1.19,2.64,2.65v15.61h5.52v-15.61c0-1.46,1.19-2.65,2.64-2.65h2.78v18.25h6.09v-18.25h3.48c1.46,0,2.64,1.19,2.64,2.65v15.61h5.52v-31.43h5.58c1.72,0,3.12,1.4,3.12,3.12v28.31h6.09v-28.31c0-1.72,1.4-3.12,3.12-3.12h14.1c1.72,0,3.12,1.4,3.12,3.12v26.88l2.19-1.49.36-.31c.2-.19.39-.39.63-.68l.14-.18c.5-.72.92-1.47,1.33-2.2.43-.76.83-1.47,1.31-2.15l.14-.19v-85.42c0-2.74-1.39-5.24-3.72-6.68ZM119.57,217.55c-2.08,0-3.78,1.69-3.78,3.78v1.06h-2.17v-1.06c0-2.08-1.69-3.78-3.78-3.78h-11.1c-2.08,0-3.78,1.69-3.78,3.78v1.06h-2.17v-52.82c0-1.7.86-3.25,2.31-4.14l17.04-10.53c1.58-.97,3.53-.98,5.11,0l5.62,3.47v59.18h-3.32ZM136.61,222.38h-2.17v-1.06c0-2.08-1.69-3.78-3.78-3.78h-2.27v-9.3c0-1.46,1.19-2.65,2.64-2.65h5.56v16.78ZM154.2,204.13c-2.27,0-4.12,1.85-4.12,4.12v14.14h-2.58v-14.14c0-2.27-1.85-4.12-4.12-4.12h-12.35c-2.27,0-4.12,1.85-4.12,4.12v14.14h-2.57v-71.71c0-1.92.97-3.67,2.61-4.67l18.95-11.71c1.78-1.1,3.99-1.1,5.77,0l5.29,3.27v66.56h-2.78ZM173.24,222.38h-2.58v-14.14c0-2.27-1.85-4.12-4.12-4.12h-3.48v-8.58c0-1.72,1.4-3.12,3.12-3.12h7.05v29.96ZM214.47,214.78c-.46.68-.86,1.39-1.25,2.08-.4.7-.8,1.42-1.26,2.08l-.1.14c-.15.17-.29.33-.46.48l-.07.06v-24.07c0-2.53-2.06-4.6-4.59-4.6h-14.1c-2.53,0-4.6,2.06-4.6,4.6v26.83h-3.15v-26.83c0-2.53-2.06-4.6-4.59-4.6h-14.1c-2.53,0-4.59,2.06-4.59,4.6v26.83h-3.15v-92.56c0-2.23,1.13-4.25,3.03-5.42l21.63-13.37c2.07-1.28,4.64-1.28,6.7,0l21.63,13.37c1.89,1.17,3.03,3.2,3.03,5.43v84.96Z"
/>
<g>
<path
className={classes.whiteLine}
d="M59.52,202.99l-.82.82c.66.66,1.02,1.53,1.02,2.46s-.36,1.81-1.02,2.46c-1.36,1.36-3.57,1.36-4.93,0-1.36-1.36-1.36-3.57,0-4.93l-.82-.82c-1.81,1.81-1.81,4.76,0,6.57.91.91,2.1,1.36,3.28,1.36s2.38-.45,3.28-1.36c.88-.88,1.36-2.04,1.36-3.28s-.48-2.41-1.36-3.28Z"
/>
<rect className={classes.whiteLine} x="55.66" y="201.4" width="1.16" height="5.47" />
</g>
<g>
<path
className={classes.whiteLine}
d="M91,208.58c.18.28.41.5.7.64.29.15.61.22.95.22.3,0,.57-.04.8-.12s.44-.18.62-.31c.18-.13.34-.26.48-.41v1.66c-.26.2-.53.36-.84.47-.3.12-.69.17-1.16.17-.51,0-.98-.08-1.41-.24s-.8-.39-1.11-.68c-.31-.3-.55-.65-.72-1.06-.17-.41-.26-.86-.26-1.36s.08-.95.26-1.36c.17-.41.41-.76.72-1.06.31-.3.68-.53,1.11-.68.43-.16.9-.24,1.41-.24.47,0,.85.06,1.16.17.3.12.58.27.84.47v1.66c-.14-.15-.3-.28-.48-.41-.18-.13-.39-.23-.62-.31s-.5-.11-.8-.11c-.35,0-.66.07-.95.22s-.52.36-.7.64c-.18.28-.26.62-.26,1.01s.09.72.26,1.01Z"
/>
<path
className={classes.whiteLine}
d="M96.04,204.89l3.06-.62v6.5h-1.49v-4.93l-1.58.39v-1.34Z"
/>
<path
className={classes.whiteLine}
d="M132.85,208.58c.18.28.41.5.7.64.29.15.61.22.95.22.3,0,.57-.04.8-.12s.44-.18.62-.31c.18-.13.34-.26.48-.41v1.66c-.26.2-.53.36-.84.47-.3.12-.69.17-1.16.17-.51,0-.98-.08-1.41-.24s-.8-.39-1.11-.68c-.31-.3-.55-.65-.72-1.06-.17-.41-.26-.86-.26-1.36s.08-.95.26-1.36c.17-.41.41-.76.72-1.06.31-.3.68-.53,1.11-.68.43-.16.9-.24,1.41-.24.47,0,.85.06,1.16.17.3.12.58.27.84.47v1.66c-.14-.15-.3-.28-.48-.41-.18-.13-.39-.23-.62-.31s-.5-.11-.8-.11c-.35,0-.66.07-.95.22s-.52.36-.7.64c-.18.28-.26.62-.26,1.01s.09.72.26,1.01Z"
/>
<path
className={classes.whiteLine}
d="M140.31,207.82c.17-.19.32-.36.44-.51.12-.15.22-.3.28-.45.06-.15.1-.31.1-.48,0-.1-.02-.19-.05-.29-.04-.1-.09-.18-.16-.26s-.16-.14-.27-.18c-.11-.05-.24-.07-.38-.07-.19,0-.37.05-.51.15-.15.1-.26.23-.34.41-.08.18-.11.38-.11.61h-1.51c0-.46.09-.87.28-1.25.19-.38.47-.68.85-.9.38-.22.84-.34,1.39-.34.39,0,.74.06,1.05.18.3.12.56.28.76.47.2.2.36.41.46.65.1.24.15.48.15.72,0,.35-.08.66-.26.95-.17.29-.39.55-.67.79l-1.49,1.33h2.55v1.41h-5.4l2.84-2.94Z"
/>
<path
className={classes.whiteLine}
d="M176.05,208.58c.18.28.41.5.7.64.29.15.61.22.95.22.3,0,.57-.04.8-.12s.44-.18.62-.31c.18-.13.34-.26.48-.41v1.66c-.26.2-.53.36-.84.47-.3.12-.69.17-1.16.17-.51,0-.98-.08-1.41-.24s-.8-.39-1.11-.68c-.31-.3-.55-.65-.72-1.06-.17-.41-.26-.86-.26-1.36s.08-.95.26-1.36c.17-.41.41-.76.72-1.06.31-.3.68-.53,1.11-.68.43-.16.9-.24,1.41-.24.47,0,.85.06,1.16.17.3.12.58.27.84.47v1.66c-.14-.15-.3-.28-.48-.41-.18-.13-.39-.23-.62-.31s-.5-.11-.8-.11c-.35,0-.66.07-.95.22s-.52.36-.7.64c-.18.28-.26.62-.26,1.01s.09.72.26,1.01Z"
/>
<path
className={classes.whiteLine}
d="M182.34,210.74c-.3-.11-.56-.28-.77-.48s-.37-.44-.48-.7-.17-.53-.17-.81h1.51c0,.21.05.38.14.52.09.14.21.24.37.31.15.07.32.1.49.1.18,0,.34-.03.48-.08.14-.05.24-.14.32-.26s.12-.27.12-.45c0-.13-.03-.26-.08-.36s-.14-.2-.25-.29-.25-.14-.42-.19c-.17-.04-.37-.06-.6-.06v-.78c.42,0,.8.03,1.15.1s.65.18.92.33c.26.15.46.34.6.57.14.23.21.51.21.83,0,.37-.1.7-.3.98-.2.28-.49.5-.86.67-.37.16-.82.24-1.34.24-.38,0-.73-.06-1.03-.17ZM182.98,206.98c.18,0,.34-.02.48-.07s.27-.11.37-.19c.11-.08.19-.17.24-.27.05-.1.08-.21.08-.33,0-.12-.03-.23-.08-.33-.05-.1-.13-.17-.24-.22-.1-.05-.24-.08-.4-.08-.24,0-.44.07-.6.21-.16.14-.24.32-.24.55h-1.41c0-.38.09-.72.28-1.02.19-.3.45-.54.79-.71.34-.17.74-.26,1.18-.26.48,0,.89.08,1.22.24.33.16.59.37.76.64s.26.56.26.89c0,.36-.12.67-.36.91-.24.24-.56.43-.97.55-.41.12-.86.18-1.37.18v-.69Z"
/>
</g>
<g>
<g>
<path
className={classes.whiteLine}
d="M14.08,77.72l3.06-.62v6.5h-1.49v-4.93l-1.58.39v-1.34Z"
/>
<path
className={classes.whiteLine}
d="M16.48,116.47c.17-.19.32-.36.44-.51.12-.15.22-.3.28-.45.06-.15.1-.31.1-.48,0-.1-.02-.19-.05-.29-.04-.1-.09-.18-.16-.26s-.16-.14-.27-.18c-.11-.05-.24-.07-.38-.07-.19,0-.37.05-.51.15-.15.1-.26.23-.34.41-.08.18-.11.38-.11.61h-1.51c0-.46.09-.87.28-1.25.19-.38.47-.68.85-.9.38-.22.84-.34,1.39-.34.39,0,.74.06,1.05.18.3.12.56.28.76.47.2.2.36.41.46.65s.15.48.15.72c0,.35-.08.66-.26.95s-.39.55-.67.79l-1.49,1.33h2.55v1.41h-5.4l2.84-2.94Z"
/>
<path
className={classes.whiteLine}
d="M15.32,155.21c-.3-.12-.56-.28-.77-.48-.21-.21-.37-.44-.48-.7s-.17-.53-.17-.81h1.51c0,.21.05.38.14.52s.21.24.37.31.32.1.49.1c.18,0,.34-.03.48-.08.14-.05.24-.14.32-.26.08-.12.12-.27.12-.45,0-.13-.03-.26-.08-.36-.05-.11-.14-.2-.25-.29s-.25-.14-.42-.19c-.17-.04-.37-.06-.6-.06v-.78c.42,0,.8.03,1.15.1.35.07.65.18.92.33.26.15.46.34.6.57.14.23.21.51.21.83,0,.37-.1.7-.3.98-.2.28-.49.5-.86.67s-.82.24-1.34.24c-.38,0-.73-.06-1.03-.17ZM15.96,151.45c.18,0,.34-.02.48-.07.14-.05.27-.11.37-.19s.19-.17.24-.27.08-.21.08-.33c0-.12-.03-.23-.08-.33s-.13-.17-.24-.22c-.1-.05-.24-.08-.4-.08-.24,0-.44.07-.6.21-.16.14-.24.32-.24.55h-1.41c0-.38.09-.72.28-1.02.19-.3.45-.54.79-.71.34-.17.74-.26,1.18-.26.48,0,.89.08,1.22.24.33.16.59.37.76.64.17.27.26.56.26.89,0,.36-.12.67-.36.91-.24.24-.56.43-.97.55-.41.12-.86.18-1.37.18v-.69Z"
/>
<path
className={classes.whiteLine}
d="M17.28,184.68h1.15v6.38h-1.6v-1.6l.17-.1v-2.44l-1.38,1.9h2.35l.15-.2h1.3v1.31h-5.85l3.7-5.25Z"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M16.95,38.26c-3.47,0-6.28,2.81-6.28,6.28s2.81,6.28,6.28,6.28,6.28-2.81,6.28-6.28-2.81-6.28-6.28-6.28ZM16.95,48.51c-2.2,0-3.98-1.78-3.98-3.98s1.78-3.98,3.98-3.98,3.98,1.78,3.98,3.98-1.78,3.98-3.98,3.98Z"
/>
<circle className={classes.whiteLine} cx="16.95" cy="44.53" r="2.33" />
</g>
</g>
<g>
<path
className={classes.whiteLine}
d="M184.22,153.42c0-2.75-1.07-5.34-3.02-7.28-.76-.76-2-.76-2.76,0-.76.76-.76,2,0,2.76,1.21,1.21,1.87,2.81,1.87,4.52s-.66,3.31-1.87,4.52c-.38.38-.57.88-.57,1.38s.19,1,.57,1.38c.76.76,2,.76,2.76,0,1.95-1.94,3.02-4.53,3.02-7.28ZM190.65,153.42c0-4.47-1.74-8.67-4.9-11.83-.76-.76-2-.76-2.76,0-.76.76-.76,2,0,2.76,2.42,2.42,3.76,5.64,3.76,9.07s-1.33,6.65-3.76,9.07c-.38.38-.57.88-.57,1.38s.19,1,.57,1.38c.76.76,2,.76,2.76,0,3.16-3.16,4.9-7.36,4.9-11.83ZM190.31,169.82c4.38-4.38,6.79-10.2,6.79-16.39s-2.41-12.02-6.79-16.39c-.76-.76-2-.76-2.76,0-.38.38-.57.88-.57,1.38s.19,1,.57,1.38c3.64,3.64,5.65,8.48,5.65,13.63s-2.01,9.99-5.65,13.63c-.76.76-.76,2,0,2.76.76.76,2,.76,2.76,0Z"
/>
<path
className={classes.whiteLine}
d="M175.74,149.01c-.62-.62-1.8-.62-2.41,0-.67.67-.67,1.75,0,2.42.53.53.83,1.24.83,2s-.29,1.46-.83,2c-.32.32-.5.75-.5,1.21s.18.88.5,1.21c.32.32.75.5,1.21.5s.88-.18,1.21-.5c1.18-1.18,1.83-2.74,1.83-4.41s-.65-3.23-1.83-4.41Z"
/>
</g>
<g>
<g>
<g>
<path
className={classes.whiteLine}
d="M124.72,103.81v.94c0,.42.32.77.73.82v.86h.2v-.86c.41-.05.73-.4.73-.82v-.94l-.83.71-.83-.71ZM126.17,104.75c0,.31-.23.57-.53.62v-.3h-.2v.3c-.3-.05-.53-.31-.53-.62v-.51l.63.54.63-.54v.51Z"
/>
<path
className={classes.whiteLine}
d="M124.72,102.37v.94c0,.46.37.83.83.83s.83-.37.83-.83v-.94l-.83.71-.83-.71ZM126.17,103.31c0,.31-.23.57-.53.62v-.3h-.2v.3c-.3-.05-.53-.31-.53-.62v-.51l.63.54.63-.54v.51Z"
/>
<path
className={classes.whiteLine}
d="M124.72,100.91v.94c0,.46.37.83.83.83s.83-.37.83-.83v-.94l-.83.71-.83-.71ZM126.17,101.85c0,.31-.23.57-.53.62v-.3h-.2v.3c-.3-.05-.53-.31-.53-.62v-.51l.63.54.63-.54v.51Z"
/>
<path
className={classes.whiteLine}
d="M125.54,101.3h.01s.01,0,.01,0c.01,0,.36-.05.48-.33.11-.27-.03-.63-.41-1.09l-.08-.09-.08.09c-.38.46-.52.82-.41,1.09.11.29.46.33.48.33ZM125.55,100.1c.34.43.36.67.31.8-.04.1-.14.16-.22.19v-.28h-.2v.27c-.07-.03-.16-.08-.2-.18-.05-.12-.03-.36.31-.8Z"
/>
<path
className={classes.whiteLine}
d="M140.2,103.6h-2.45v.99c-.25.14-.54.23-.85.23-.93,0-1.69-.76-1.69-1.69s.76-1.69,1.69-1.69,1.67.73,1.69,1.65h1.64c-.02-1.82-1.51-3.29-3.33-3.29-1.54,0-2.83,1.04-3.22,2.46-.38-1.42-1.68-2.46-3.22-2.46-1.84,0-3.34,1.49-3.34,3.34s1.49,3.34,3.34,3.34c.62,0,1.2-.17,1.69-.46v.46h1.64v-2.11c.49,1.24,1.69,2.11,3.1,2.11.62,0,1.2-.17,1.69-.46v.46h1.64v-2.87h-.03ZM130.46,104.82c-.93,0-1.69-.76-1.69-1.69s.76-1.69,1.69-1.69,1.69.76,1.69,1.69-.76,1.69-1.69,1.69Z"
/>
</g>
<g>
<path
className={classes.whiteLine}
d="M143.16,102.83l-.27-.61h-.94l-.28.61h-.41l1.15-2.47,1.15,2.47h-.41ZM142.09,101.9h.66l-.24-.54s-.03-.07-.04-.12c-.02-.04-.03-.09-.05-.15-.01.05-.03.1-.04.15-.01.04-.03.09-.04.12l-.24.54Z"
/>
<path
className={classes.whiteLine}
d="M144.32,102.83v-2.38h.52c.34,0,.58.02.71.05.14.03.26.09.36.17.14.1.24.24.31.4.07.16.11.35.11.56s-.04.4-.11.56c-.07.16-.17.3-.31.4-.1.08-.22.14-.35.17-.13.03-.34.05-.62.05h-.62ZM144.71,102.49h.32c.18,0,.31-.01.4-.04.09-.02.17-.06.23-.12.09-.08.16-.17.2-.29.04-.11.07-.25.07-.4s-.02-.28-.07-.4c-.04-.11-.11-.21-.2-.29-.07-.06-.15-.1-.24-.12-.09-.02-.24-.04-.44-.04h-.28v1.68Z"
/>
<path
className={classes.whiteLine}
d="M148.82,102.83l-.27-.61h-.94l-.28.61h-.41l1.15-2.47,1.15,2.47h-.41ZM147.76,101.9h.66l-.24-.54s-.03-.07-.04-.12c-.02-.04-.03-.09-.05-.15-.01.05-.03.1-.04.15-.01.04-.03.09-.04.12l-.24.54Z"
/>
<path
className={classes.whiteLine}
d="M150.35,101.77v1.07h-.36v-2.38h.58c.17,0,.3,0,.38.03.08.02.15.05.21.09.07.06.13.13.18.22.04.09.06.2.06.31s-.02.22-.06.31c-.04.1-.1.17-.18.22-.06.04-.13.07-.21.09-.08.02-.21.03-.38.03h-.22ZM150.37,101.45h.1c.21,0,.36-.02.43-.07s.11-.13.11-.25c0-.13-.04-.22-.11-.27-.08-.05-.22-.08-.42-.08h-.1v.66Z"
/>
<path
className={classes.whiteLine}
d="M153.05,100.79v2.05h-.39v-2.05h-.63v-.33h1.65v.33h-.63Z"
/>
<path className={classes.whiteLine} d="M154.52,102.83v-2.38h.39v2.38h-.39Z" />
<path
className={classes.whiteLine}
d="M156.74,102.93l-1.06-2.47h.4l.53,1.27c.03.07.06.14.08.2.02.06.04.12.05.18.01-.06.03-.12.05-.18.02-.06.05-.13.08-.2l.53-1.27h.4l-1.06,2.47Z"
/>
<path
className={classes.whiteLine}
d="M158.57,102.83v-2.38h1.33v.33h-.94v.6h.94v.34h-.94v.77h.94v.35h-1.33Z"
/>
<path
className={classes.whiteLine}
d="M143.16,106.36l-.27-.61h-.94l-.28.61h-.41l1.15-2.47,1.15,2.47h-.41ZM142.09,105.43h.66l-.24-.54s-.03-.07-.04-.12c-.02-.04-.03-.09-.05-.15-.01.05-.03.1-.04.15-.01.04-.03.09-.04.12l-.24.54Z"
/>
<path
className={classes.whiteLine}
d="M145.58,105.15h.94s0,.04,0,.07c0,.08,0,.13,0,.16,0,.33-.1.59-.29.78s-.46.28-.81.28c-.19,0-.37-.03-.52-.09-.15-.06-.29-.14-.4-.26-.11-.11-.2-.25-.26-.4-.06-.15-.09-.32-.09-.5s.03-.35.09-.5c.06-.15.15-.29.27-.4.12-.11.25-.2.41-.26.16-.06.32-.09.5-.09.19,0,.35.03.5.1.15.07.28.17.4.31l-.28.21c-.08-.09-.17-.16-.27-.21-.1-.05-.21-.07-.34-.07-.26,0-.47.08-.63.25-.16.16-.24.38-.24.64s.08.49.24.65c.16.17.37.25.63.25.22,0,.39-.05.52-.15.13-.1.19-.24.19-.42v-.03h-.57v-.33Z"
/>
<path
className={classes.whiteLine}
d="M147.83,105.3v1.06h-.36v-2.38h.54c.16,0,.28.01.37.03.09.02.16.05.22.1.07.06.13.13.17.22.04.09.06.19.06.3,0,.19-.05.34-.14.45-.09.11-.23.17-.41.19l.82,1.08h-.44l-.79-1.06h-.04ZM147.85,105h.07c.2,0,.34-.02.41-.07.07-.05.1-.13.1-.25,0-.13-.04-.22-.11-.28-.07-.05-.21-.08-.4-.08h-.07v.68Z"
/>
<path className={classes.whiteLine} d="M149.86,106.36v-2.38h.39v2.38h-.39Z" />
<path
className={classes.whiteLine}
d="M153.24,104.57c-.11-.09-.22-.16-.35-.21-.12-.05-.25-.07-.38-.07-.26,0-.47.08-.63.25-.16.17-.24.38-.24.65s.08.47.24.63c.16.17.36.25.6.25.14,0,.27-.02.4-.07.13-.05.25-.12.37-.23v.44c-.1.07-.22.13-.34.17-.12.04-.25.06-.4.06-.18,0-.35-.03-.5-.09s-.29-.15-.41-.26c-.12-.11-.2-.25-.27-.4-.06-.15-.09-.32-.09-.49s.03-.34.09-.49c.06-.15.15-.29.27-.4.12-.12.25-.21.41-.27.15-.06.32-.09.49-.09.14,0,.27.02.4.06s.25.1.37.18l-.02.39Z"
/>
<path
className={classes.whiteLine}
d="M154.24,103.99h.38v1.44c0,.21.05.38.15.48.1.11.25.16.44.16s.34-.05.44-.16c.1-.11.15-.27.15-.48v-1.44h.39v1.48c0,.32-.08.56-.24.72-.16.16-.41.24-.73.24s-.57-.08-.73-.24c-.16-.16-.24-.4-.24-.72v-1.48Z"
/>
<path
className={classes.whiteLine}
d="M157.26,106.36v-2.38h.39v2.03h.86v.35h-1.25Z"
/>
<path
className={classes.whiteLine}
d="M159.94,104.32v2.05h-.39v-2.05h-.63v-.33h1.65v.33h-.63Z"
/>
<path
className={classes.whiteLine}
d="M161.38,103.99h.38v1.44c0,.21.05.38.15.48.1.11.25.16.44.16s.34-.05.44-.16c.1-.11.15-.27.15-.48v-1.44h.39v1.48c0,.32-.08.56-.24.72s-.41.24-.73.24-.57-.08-.73-.24c-.16-.16-.24-.4-.24-.72v-1.48Z"
/>
<path
className={classes.whiteLine}
d="M164.76,105.3v1.06h-.36v-2.38h.54c.16,0,.28.01.37.03.09.02.16.05.22.1.07.06.13.13.17.22.04.09.06.19.06.3,0,.19-.05.34-.14.45-.09.11-.23.17-.41.19l.82,1.08h-.44l-.79-1.06h-.04ZM164.78,105h.07c.2,0,.34-.02.41-.07.07-.05.1-.13.1-.25,0-.13-.04-.22-.11-.28-.07-.05-.21-.08-.4-.08h-.07v.68Z"
/>
<path
className={classes.whiteLine}
d="M166.79,106.36v-2.38h1.33v.33h-.94v.6h.94v.34h-.94v.77h.94v.35h-1.33Z"
/>
</g>
</g>
<g>
<path
className={classes.whiteLine}
d="M96.59,99.33v3.04h-.62v-3.04h.62ZM96.18,100.47h2.23v.56h-2.23v-.56ZM98.63,99.33v3.04h-.62v-3.04h.62Z"
/>
<path
className={classes.whiteLine}
d="M99.68,101.9s.07.07.12.1c.05.02.11.03.17.03.09,0,.16-.02.23-.05.07-.04.13-.09.17-.16.04-.07.06-.15.06-.25l.07.26c0,.13-.04.23-.11.32s-.16.15-.27.2c-.11.04-.23.07-.35.07s-.24-.02-.35-.07c-.11-.05-.19-.12-.26-.22-.07-.09-.1-.21-.1-.35,0-.19.07-.35.2-.46.14-.11.33-.17.58-.17.13,0,.24.01.34.04.1.03.18.06.25.1.07.04.11.07.14.11v.26c-.08-.06-.17-.1-.26-.13-.1-.03-.2-.04-.31-.04-.09,0-.16.01-.21.03-.06.02-.1.05-.12.09-.03.04-.04.09-.04.15s.01.11.04.15ZM99.21,100.55c.1-.05.23-.11.38-.16s.32-.08.5-.08c.17,0,.32.02.46.07.13.05.24.12.31.21.08.09.11.21.11.35v1.42h-.54v-1.3c0-.06,0-.1-.03-.14-.02-.04-.04-.07-.08-.1-.04-.03-.08-.04-.13-.06-.05-.01-.1-.02-.17-.02-.09,0-.18.01-.26.03-.08.02-.16.04-.22.07-.06.03-.11.05-.13.07l-.21-.38Z"
/>
<path
className={classes.whiteLine}
d="M102.05,102.37h-.55v-2h.55v2ZM102.52,100.88s-.09-.03-.15-.03c-.07,0-.13.02-.18.06-.05.04-.09.09-.11.16-.02.07-.04.15-.04.25l-.16-.16c0-.16.03-.3.09-.42.06-.12.14-.22.24-.3.1-.07.2-.11.3-.11.07,0,.14.01.21.04.06.03.12.07.16.12l-.26.48s-.08-.06-.12-.08Z"
/>
<path
className={classes.whiteLine}
d="M103.19,100.8c.09-.15.21-.27.36-.35.15-.08.3-.12.47-.12s.32.04.45.13c.13.08.23.2.3.36s.11.34.11.56-.04.4-.11.56-.18.28-.3.36c-.13.08-.28.13-.45.13s-.32-.04-.47-.12c-.15-.08-.27-.2-.36-.36-.09-.16-.13-.34-.13-.56s.04-.41.13-.57ZM103.71,101.68c.05.09.12.15.2.2.08.05.17.07.27.07.08,0,.17-.02.25-.07.08-.04.14-.11.2-.2.05-.09.08-.19.08-.31s-.03-.23-.08-.31c-.05-.09-.12-.15-.2-.2-.08-.04-.16-.07-.25-.07-.1,0-.19.02-.27.07-.08.05-.15.11-.2.2-.05.09-.07.19-.07.31s.02.22.07.31ZM105.26,98.98v3.39h-.56v-3.39h.56Z"
/>
<path
className={classes.whiteLine}
d="M106.11,100.37l.44,1.11.56-1.26.59,1.26.44-1.11h.59l-.98,2.13-.64-1.27-.62,1.27-.98-2.13h.6Z"
/>
<path
className={classes.whiteLine}
d="M109.44,101.9s.07.07.12.1c.05.02.11.03.17.03.09,0,.16-.02.23-.05.07-.04.13-.09.17-.16.04-.07.06-.15.06-.25l.07.26c0,.13-.04.23-.11.32s-.16.15-.27.2c-.11.04-.23.07-.35.07s-.24-.02-.35-.07c-.11-.05-.19-.12-.26-.22-.07-.09-.1-.21-.1-.35,0-.19.07-.35.2-.46.14-.11.33-.17.58-.17.13,0,.24.01.34.04.1.03.18.06.25.1.07.04.11.07.14.11v.26c-.08-.06-.17-.1-.26-.13-.1-.03-.2-.04-.31-.04-.09,0-.16.01-.21.03-.06.02-.1.05-.12.09-.03.04-.04.09-.04.15s.01.11.04.15ZM108.98,100.55c.1-.05.23-.11.38-.16s.32-.08.5-.08c.17,0,.32.02.46.07.13.05.24.12.31.21.08.09.11.21.11.35v1.42h-.54v-1.3c0-.06,0-.1-.03-.14-.02-.04-.04-.07-.08-.1-.04-.03-.08-.04-.13-.06-.05-.01-.1-.02-.17-.02-.09,0-.18.01-.26.03-.08.02-.16.04-.22.07-.06.03-.11.05-.13.07l-.21-.38Z"
/>
<path
className={classes.whiteLine}
d="M111.82,102.37h-.55v-2h.55v2ZM112.29,100.88s-.09-.03-.15-.03c-.07,0-.13.02-.18.06-.05.04-.09.09-.11.16-.02.07-.04.15-.04.25l-.16-.16c0-.16.03-.3.09-.42.06-.12.14-.22.24-.3.1-.07.2-.11.3-.11.07,0,.14.01.21.04.07.03.12.07.16.12l-.26.48s-.08-.06-.11-.08Z"
/>
<path
className={classes.whiteLine}
d="M113.27,102.28c-.16-.09-.29-.21-.38-.36-.09-.15-.13-.34-.13-.55s.04-.4.13-.55c.09-.15.21-.27.38-.36.16-.09.35-.13.57-.13s.41.04.56.12.27.2.35.35.12.34.12.55c0,.03,0,.05,0,.08,0,.03,0,.05,0,.07h-1.79v-.35h1.3l-.15.2s.02-.04.03-.07c.01-.03.02-.06.02-.08,0-.1-.02-.18-.05-.25-.04-.07-.09-.12-.15-.17-.07-.04-.14-.06-.23-.06-.11,0-.2.02-.28.07-.07.05-.13.12-.17.21-.04.09-.06.21-.06.35,0,.13.02.25.06.34.04.09.1.16.18.21.08.05.17.07.28.07.12,0,.23-.02.32-.07s.16-.12.22-.21l.49.15c-.1.19-.24.33-.41.42-.17.09-.38.14-.62.14-.22,0-.41-.04-.57-.13Z"
/>
<path
className={classes.whiteLine}
d="M117.16,102.37h-.56v-3.39h.56v3.39ZM118.66,101.93c-.09.16-.21.28-.35.36s-.3.12-.47.12-.31-.04-.44-.13c-.13-.08-.23-.2-.31-.36s-.11-.34-.11-.56.04-.4.11-.56.18-.27.31-.36c.13-.08.28-.13.44-.13s.32.04.47.12.26.2.35.35c.09.16.13.34.13.57s-.04.41-.13.56ZM118.14,101.06c-.05-.09-.11-.15-.2-.2s-.17-.07-.27-.07c-.08,0-.17.02-.25.07-.08.04-.14.11-.2.2s-.08.19-.08.31.03.23.08.31.12.15.2.2c.08.05.16.07.25.07.1,0,.19-.02.27-.07s.15-.11.2-.2c.05-.09.07-.19.07-.31s-.02-.22-.07-.31Z"
/>
<path
className={classes.whiteLine}
d="M119.75,103.32h-.58l.5-1.15-.83-1.8h.63l.64,1.59h-.28s.63-1.59.63-1.59h.59l-1.3,2.95Z"
/>
<path
className={classes.whiteLine}
d="M121.46,100.23c.07-.07.15-.1.25-.1s.19.03.26.1c.07.07.1.15.1.25s-.03.18-.1.25c-.07.07-.15.1-.26.1s-.19-.03-.25-.1c-.07-.07-.1-.15-.1-.25s.03-.18.1-.25ZM121.46,101.86c.07-.07.15-.1.25-.1s.19.03.26.1c.07.07.1.15.1.25,0,.09-.03.17-.1.24-.07.07-.15.1-.26.1s-.19-.03-.25-.1c-.07-.07-.1-.15-.1-.24,0-.1.03-.18.1-.25Z"
/>
</g>
<rect className={classes.whiteLine} x="75.58" y="95.54" width="92.26" height=".81" />
<polygon
className={classes.whiteLine}
points="86.33 92.24 86.33 93.96 88.81 95.94 86.33 97.92 86.33 99.64 90.96 95.94 86.33 92.24"
/>
<polygon
className={classes.whiteLine}
points="81.86 92.24 81.86 93.96 84.33 95.94 81.86 97.92 81.86 99.64 86.49 95.94 81.86 92.24"
/>
<path
className={classes.whiteLine}
d="M58.9,103.34c-.23,0-.46-.02-.69-.05-.16-.02-.31-.12-.39-.27s-.08-.33-.01-.48h0c.47-.97,1.22-1.76,2.18-2.27.95-.51,2.02-.71,3.1-.56.16.02.31.12.39.27s.08.33.01.48c-.47.97-1.22,1.76-2.18,2.27-.75.4-1.57.61-2.4.61ZM58.38,102.82h0ZM58.46,102.67c.88.09,1.75-.09,2.53-.51.78-.42,1.41-1.05,1.82-1.83-.88-.09-1.75.09-2.53.51-.78.42-1.41,1.05-1.82,1.83Z"
/>
<path
className={classes.whiteLine}
d="M63.9,97.27s-.07,0-.1,0c-1.08-.02-2.11-.38-2.97-1.03-.13-.1-.21-.26-.21-.43s.09-.33.22-.42h0c.89-.62,1.91-.94,3.01-.91,1.08.02,2.11.38,2.97,1.03.13.1.21.26.21.43,0,.17-.09.33-.22.42-.86.6-1.86.91-2.91.91ZM61.36,95.83c.72.5,1.57.78,2.45.8.91.02,1.74-.22,2.48-.7-.72-.5-1.57-.78-2.45-.8-.89-.02-1.74.22-2.48.7Z"
/>
<path
className={classes.whiteLine}
d="M75.39,95.72c-.97,0-1.91-.27-2.73-.8-.91-.58-1.61-1.42-2-2.42-.06-.15-.04-.33.05-.47.09-.14.24-.23.41-.24h0c1.08-.07,2.13.21,3.05.79.91.58,1.61,1.42,2,2.42.06.15.04.33-.05.47-.09.14-.24.23-.41.24-.11,0-.21.01-.32.01ZM71.33,92.42c.35.81.93,1.48,1.68,1.96.75.48,1.6.71,2.49.69-.35-.81-.93-1.48-1.68-1.96-.75-.48-1.59-.71-2.49-.69ZM71.15,92.43h0Z"
/>
<path
className={classes.whiteLine}
d="M71.29,100.11c-.11,0-.21,0-.32-.01-.16,0-.32-.1-.41-.24-.09-.14-.11-.32-.05-.47.4-1.01,1.09-1.84,2-2.43.91-.58,1.96-.85,3.05-.79.17.01.32.1.41.24s.11.32.05.47c-.4,1.01-1.09,1.84-2,2.42-.82.52-1.76.8-2.73.8ZM71.18,99.46c.89.02,1.74-.22,2.49-.69.75-.48,1.33-1.15,1.68-1.96-.88-.01-1.74.22-2.49.69-.75.48-1.33,1.15-1.68,1.96Z"
/>
<path
className={classes.whiteLine}
d="M70.32,95.72c-.97,0-1.91-.27-2.73-.8-.91-.58-1.61-1.42-2-2.42-.06-.15-.04-.33.05-.47.09-.14.24-.23.41-.24h0c1.08-.07,2.13.21,3.05.79.91.58,1.61,1.42,2,2.42.06.15.04.33-.05.47-.09.14-.24.23-.41.24-.11,0-.21.01-.32.01ZM66.25,92.42c.35.81.93,1.48,1.68,1.96.75.48,1.59.7,2.49.69-.35-.81-.93-1.48-1.68-1.96s-1.61-.71-2.49-.69ZM66.08,92.43h0Z"
/>
<path
className={classes.whiteLine}
d="M66.22,100.11c-.11,0-.21,0-.32-.01-.16,0-.32-.1-.41-.24-.09-.14-.11-.32-.05-.47.4-1.01,1.09-1.84,2-2.43.91-.58,1.96-.85,3.05-.79.17.01.32.1.41.24.09.14.11.32.05.47-.4,1-1.09,1.84-2,2.42-.82.52-1.76.8-2.73.8ZM66.11,99.46c.88.02,1.74-.22,2.49-.69.75-.48,1.33-1.15,1.68-1.96-.88-.01-1.74.22-2.49.69-.75.48-1.33,1.15-1.68,1.96ZM70.45,96.82h0,0ZM70.35,96.65h0s0,0,0,0Z"
/>
<path
className={classes.whiteLine}
d="M80.46,95.72c-.97,0-1.91-.27-2.73-.8-.91-.58-1.61-1.42-2-2.42-.06-.15-.04-.33.05-.47.09-.14.24-.23.41-.24h0c1.09-.07,2.13.21,3.05.79.91.58,1.61,1.42,2,2.42.06.15.04.33-.05.47-.09.14-.24.23-.41.24-.11,0-.21.01-.32.01ZM76.4,92.42c.35.81.93,1.48,1.68,1.96.75.48,1.59.7,2.49.69-.35-.81-.93-1.48-1.68-1.96s-1.61-.71-2.49-.69ZM76.22,92.43h0Z"
/>
<path
className={classes.whiteLine}
d="M76.36,100.11c-.11,0-.21,0-.32-.01-.16,0-.32-.1-.41-.24-.09-.14-.11-.32-.05-.47.4-1.01,1.09-1.84,2-2.43.91-.58,1.97-.85,3.05-.79.17.01.32.1.41.24.09.14.11.32.05.47-.4,1-1.09,1.84-2,2.42-.82.52-1.76.8-2.73.8ZM76.25,99.46c.88.02,1.74-.22,2.49-.69.75-.48,1.33-1.15,1.68-1.96-.87-.01-1.74.22-2.49.69-.75.48-1.33,1.15-1.68,1.96ZM80.59,96.82h0,0ZM80.49,96.65h0s0,0,0,0Z"
/>
<path
className={classes.whiteLine}
d="M57.02,98.03c-.68,0-1.36-.14-2-.41-1-.43-1.81-1.14-2.37-2.07-.09-.14-.1-.32-.03-.48s.2-.27.36-.31c1.05-.24,2.14-.14,3.13.28,1,.43,1.81,1.14,2.37,2.07.09.14.1.32.03.47-.07.16-.2.27-.36.31-.38.09-.76.13-1.14.13ZM53.3,95.36c.48.74,1.16,1.31,1.98,1.66.82.35,1.7.45,2.57.28-.48-.74-1.16-1.31-1.98-1.66-.82-.35-1.7-.44-2.57-.28ZM53.13,95.4h0,0Z"
/>
<g>
<path
className={classes.whiteLine}
d="M61.04,86.66c0,2.09-1.71,3.3-4.23,3.3-1.83,0-3.37-.63-4.33-1.24l1.01-2.04c.92.52,2.13,1.02,3.3,1.02.69,0,1.6-.17,1.6-.81,0-1.54-5.44-.49-5.51-4.1-.03-1.99,1.6-3.27,4.17-3.27,1.37,0,2.46.36,3.45.89l-.94,2.01c-.92-.45-1.97-.71-2.75-.71-.92,0-1.35.39-1.35.82,0,1.58,5.58.33,5.58,4.12Z"
/>
<path
className={classes.whiteLine}
d="M72.92,81.95h-2.96v7.82h-2.59v-7.82h-2.96v-2.26h8.52v2.26Z"
/>
<path
className={classes.whiteLine}
d="M81.4,86.86h-2.01v2.91h-2.59v-10.07h5.38c2.42,0,3.77,1.47,3.77,3.47,0,1.41-.66,2.63-1.93,3.27l2.46,3.34h-3.07l-2.01-2.91ZM79.39,84.61h2.25c1.28,0,1.67-.65,1.67-1.4,0-.88-.5-1.27-1.64-1.27h-2.27v2.66Z"
/>
<path
className={classes.whiteLine}
d="M92.28,83.56h5.11v2.2h-5.11v1.74h5.61v2.26h-8.18v-10.07h8.15v2.26h-5.58v1.61Z"
/>
<path
className={classes.whiteLine}
d="M107.73,88.27h-4.65l-.6,1.5h-2.79l4.48-10.07h2.5l4.48,10.07h-2.81l-.6-1.5ZM106.86,86.09l-1.19-2.94-.24-.66-.24.66-1.19,2.94h2.88Z"
/>
<path
className={classes.whiteLine}
d="M125.4,89.76h-2.59v-6l-.19.33-2.43,2.89h-.63l-2.43-2.89-.2-.35v6.02h-2.59v-10.07h1.86l3.38,3.83.3.45.29-.45,3.38-3.83h1.86v10.07Z"
/>
<path
className={classes.whiteLine}
d="M136.7,89.76h-7.38v-10.07h2.59v7.82h4.79v2.26Z"
/>
<path className={classes.whiteLine} d="M142.56,89.76h-2.59v-10.07h2.59v10.07Z" />
<path
className={classes.whiteLine}
d="M156.7,89.76h-1.91l-4.94-5.94-.2-.39v6.33h-2.59v-10.07h2.26l4.59,5.51.2.37v-5.89h2.59v10.07Z"
/>
<path
className={classes.whiteLine}
d="M163.11,83.56h5.11v2.2h-5.11v1.74h5.61v2.26h-8.18v-10.07h8.15v2.26h-5.58v1.61Z"
/>
</g>
</g>
{/* buttons for port selection */}
{/* I2C PORTS */}
<ellipse
onClick={e => {
openMenu(e, {
address: 0, //address here is not important for I2C, the restrictions are per component not per port
addressType: quack.AddressType.ADDRESS_TYPE_I2C,
label: "1",
autoDetectable: false
});
}}
className={classes.testButton}
id="button"
cx="17"
cy="44"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* CONFIGURABLE PIN PORTS - Note input Ports 3 and 4 and all control ports are locked for the monitor */}
{/* PORT 1 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 1,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "1",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="17"
cy="80"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* PORT 2 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 2,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "2",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="17"
cy="116"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* PORT 3 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 4,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "3",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="17"
cy="152"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* Port 4 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 8,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "4",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="17"
cy="188"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* Controller ports - note these are all locked for monitor devices */}
{/* PORT C1 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 2,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "C1",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="95"
cy="207"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* PORT C2 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 4,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "C2",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="138"
cy="207"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
{/* Port C3 */}
<ellipse
onClick={e => {
openMenu(e, {
address: 8,
addressType: quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY,
label: "C3",
autoDetectable: true
});
}}
className={classes.testButton}
id="button"
cx="180"
cy="207"
rx={buttonSize.rxLarge}
ry={buttonSize.ryLarge}
/>
</svg>
);
};
const getProductSVG = () => {
switch (device.settings.product) {
case pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO:
@ -5232,6 +6159,10 @@ export default function DeviceSVG(props: Props) {
return miVentSVG();
case pond.DeviceProduct.DEVICE_PRODUCT_MIVENT_V2:
return miVentSVG(true);
case pond.DeviceProduct.DEVICE_PRODUCT_STREAMLINE_MONITOR:
return streamlineMonitorSVG();
case pond.DeviceProduct.DEVICE_PRODUCT_STREAMLINE_AUTOMATE:
return streamlineAutomateSVG();
}
};

View file

@ -79,7 +79,8 @@ class UploadFirmware extends React.Component<Props, State> {
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK,
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN,
pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE,
pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE
pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE,
pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE
].includes(platform);
var isChannelValid = [
pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA,
@ -229,6 +230,9 @@ class UploadFirmware extends React.Component<Props, State> {
</MenuItem>
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE}>
V2 Wifi Blue
</MenuItem>
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE}>
V2 Ethernet Blue
</MenuItem>
</TextField>
<TextField

View file

@ -31,6 +31,8 @@ interface Props {
drawerView?: boolean;
}
export default function GateDevice(props: Props) {
const { gate, comprehensiveDevice, linkedCompList, drawerView } = props;
const [{as}] = useGlobalState();
@ -234,6 +236,7 @@ export default function GateDevice(props: Props) {
return (
<Select
id="ambientSelect"
variant="standard"
value={ambientKey}
style={{ fontSize: 8 }}
onChange={e => {
@ -266,6 +269,7 @@ export default function GateDevice(props: Props) {
return (
<Select
id="tempSelect"
variant="standard"
value={tempKey}
style={{ fontSize: 8 }}
onChange={e => {
@ -300,6 +304,7 @@ export default function GateDevice(props: Props) {
return (
<Select
id="pressureSelect"
variant="standard"
value={pressureKey}
style={{ fontSize: 8 }}
onChange={e => {
@ -335,9 +340,10 @@ export default function GateDevice(props: Props) {
container
direction="row"
alignContent="center"
width="100%"
//alignItems="center"
>
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 8}} style={{ padding: 10 }}>
<Grid size={{ sm: 12, lg: isMobile || drawerView ? 12 : 4}} style={{ padding: 10 }}>
<Box
paddingLeft={1}
display="flex"

View file

@ -355,13 +355,13 @@ export default function GateSVG(props: Props) {
{renderValues()}
</g>
<foreignObject height={20} width={120} x={10} y={83}>
<Box style={{ position: "fixed" }}>{ambientSelector()}</Box>
<Box sx={{ position: "fixed" }}>{ambientSelector()}</Box>
</foreignObject>
<foreignObject height={20} width={120} x={10} y={118}>
<Box style={{ position: "fixed" }}>{tempChainSelector()}</Box>
<Box sx={{ position: "fixed" }}>{tempChainSelector()}</Box>
</foreignObject>
<foreignObject height={20} width={120} x={10} y={153}>
<Box style={{ position: "fixed" }}>{pressureChainSelector()}</Box>
<Box sx={{ position: "fixed" }}>{pressureChainSelector()}</Box>
</foreignObject>
{pcaRed()}
{pcaGreen()}

View file

@ -14,6 +14,7 @@ interface FeatureVersionByPlatform {
v2CellGreen: string;
v2WifiBlue: string;
v2CellBlue: string;
v2EthBlue: string;
}
const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
@ -29,7 +30,8 @@ const featureVersions: Map<string, FeatureVersionByPlatform> = new Map([
v2CellBlack: "2.0.44",
v2CellGreen: "2.0.44",
v2WifiBlue: "2.0.44",
v2CellBlue: "2.0.44"
v2CellBlue: "2.0.44",
v2EthBlue: "2.0.44"
}
]
]);
@ -118,6 +120,8 @@ export class Device {
return this.status.firmwareVersion >= versions.v2WifiBlue;
case pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE:
return this.status.firmwareVersion >= versions.v2CellBlue;
case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE:
return this.status.firmwareVersion >= versions.v2EthBlue;
default:
return false;
}

View file

@ -14,7 +14,6 @@ import { pond, quack } from "protobuf-ts/pond";
import { cloneDeep } from "lodash";
import DeviceOverview from "device/DeviceOverview";
import { DeviceAvailabilityMap, FindAvailablePositions, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability";
import AddComponentManualDialog from "component/AddComponentManualDialog";
import ComponentCard from "component/ComponentCard";
import { sameComponentID, sortComponents } from "pbHelpers/Component";
import { isController } from "pbHelpers/ComponentType";

View file

@ -23,6 +23,7 @@ import {
} from "hooks";
import InteractionChip from "interactions/InteractionChip";
import InteractionSettings from "interactions/InteractionSettings";
import { cloneDeep } from "lodash";
// import MaterialTable from "material-table";
import { Component, Device, deviceScope, Group, Interaction } from "models";
import { convertedUnitMeasurement, MeasurementsFor, UnitMeasurement } from "models/UnitMeasurement";
@ -450,7 +451,6 @@ export default function DeviceComponent() {
// });
})
.catch((err: any) => {
// resolve({
// data: [],
// page: 0,
@ -613,6 +613,32 @@ export default function DeviceComponent() {
pageSize={pageSize}
setPage={setPage}
handleRowsPerPageChange={handleRowsPerPageChange}
loadMore={() => {
let currentRows = cloneDeep(rows)
let newOffset = rows.length
componentAPI.listUnitMeasurements(
deviceID,
componentID,
startDate,
endDate,
pageSize,
newOffset,
order,
undefined,
getContextKeys(),
getContextTypes(),
showErrors,
as)
.then(resp => {
let newRows = UnitMeasurement.convertMeasurements(
resp.data.measurements.map(um => UnitMeasurement.any(um, user))
)
setRows(currentRows.concat(newRows))
}).catch(err => {
})
}}
/>
</Grid>
);

View file

@ -301,6 +301,9 @@ export default function DeviceSupport() {
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE}>
V2 Cellular Blue
</MenuItem>
<MenuItem value={pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE}>
V2 Ethernet Blue
</MenuItem>
</Select>
</FormControl>
<Button color="primary" onClick={() => updateDevicePlatform(platform)}>

View file

@ -1,5 +1,5 @@
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon } from "@mui/icons-material";
import { Box, Chip, Grid2, IconButton, Skeleton, Tab, Tabs, Theme, Tooltip, Typography, useTheme } from "@mui/material";
import { DeveloperBoard as ProvisionIcon, LibraryAdd as CreateGroupIcon, Tune } from "@mui/icons-material";
import { Box, Checkbox, Chip, Grid2, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Skeleton, Tab, Tabs, Theme, Tooltip, Typography, useTheme } from "@mui/material";
import { blue, green, orange } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import ResponsiveTable, { Column } from "common/ResponsiveTable";
@ -24,6 +24,9 @@ import { Tag as TagUI } from "common/Tag";
import moment from "moment";
import StatusPlenum from "common/StatusPlenum";
import StatusSen5x from "common/StatusSen5x";
import StatusDust from "common/StatusDust";
import StatusGas from "common/StatusGas";
import { cloneDeep } from "lodash";
const useStyles = makeStyles((theme: Theme) => {
return ({
@ -57,14 +60,14 @@ const useStyles = makeStyles((theme: Theme) => {
margin: theme.spacing(1),
marginLeft: theme.spacing(2),
display: "flex",
width: theme.spacing(32)
// width: theme.spacing(26)
// justifyContent: "center",
},
tagsCellContainer: {
margin: theme.spacing(1),
marginLeft: theme.spacing(2),
display: "flex",
width: theme.spacing(24)
width: theme.spacing(18)
// justifyContent: "center",
},
green: {
@ -92,14 +95,40 @@ export default function Devices() {
const [devicesLoading, setDevicesLoading] = useState(false)
const [limit, setLimit] = useState(10);
const [page, setPage] = useState(0);
const [order, setOrder] = useState<"asc" | "desc">("asc");
const [orderBy, setOrderBy] = useState("name");
const [order, setOrder] = useState<"asc" | "desc">(() => {
// Load from sessionStorage on initial render
const savedOrder = sessionStorage.getItem('order');
return (savedOrder === "asc" || savedOrder === "desc") ? savedOrder : "asc";
});
// Save to sessionStorage whenever order changes
useEffect(() => {
sessionStorage.setItem('order', order);
}, [order]);
const [orderBy, setOrderBy] = useState(() => {
// Load from sessionStorage on initial render
const savedOrder = sessionStorage.getItem('orderBy');
return (savedOrder && savedOrder?.length > 0) ? savedOrder : "name";
});
// Save to sessionStorage whenever order changes
useEffect(() => {
sessionStorage.setItem('orderBy', orderBy);
}, [orderBy]);
// const [orderBy, setOrderBy] = useState("name");
const [search, setSearch] = useState("");
const [total, setTotal] = useState(0);
const [devices, setDevices] = useState<Device[]>([])
const [isProvisionDialogOpen, setIsProvisionDialogOpen] = useState<boolean>(false);
const [hasPlenums, setHasPlenums] = useState(false)
const [hasSen5x, setHasSen5x] = useState(false)
const [hasCo, setHasCo] = useState(false)
const [hasCo2, setHasCo2] = useState(false)
const [hasNo2, setHasNo2] = useState(false)
const [hasO2, setHasO2] = useState(false)
const [groupsLoading, setGroupsLoading] = useState(false)
const [groups, setGroups] = useState<Group[]>([]);
@ -122,6 +151,26 @@ export default function Devices() {
const [groupSettingsIsOpen, setGroupSettingsIsOpen] = useState<boolean>(false);
const [fieldContains, setFieldContains] = useState<Map<string, string>>(new Map<string, string>())
const [separateDust, setSeparateDust] = useState(() => {
const stored = localStorage.getItem('separateDust');
return stored !== null ? stored === 'true' : false;
});
useEffect(() => {
localStorage.setItem('separateDust', separateDust.toString());
}, [separateDust]);
const [groupGas, setGroupGas] = useState(() => {
const stored = localStorage.getItem('groupGas');
return stored !== null ? stored === 'true' : false;
});
useEffect(() => {
localStorage.setItem('groupGas', groupGas.toString());
}, [groupGas]);
const [preferencesAnchor, setPreferencesAnchor] = useState<any>(null)
const [{ as }] = useGlobalState()
@ -250,12 +299,12 @@ export default function Devices() {
).then(resp => {
let newDevices: Device[] = []
resp.data.devices.forEach(device => {
if (device.status?.plenum?.temperature) {
setHasPlenums(true)
}
if (device.status?.sen5x?.temperature) {
setHasSen5x(true)
}
if (device.status?.plenum?.temperature) setHasPlenums(true)
if (device.status?.sen5x?.temperature) setHasSen5x(true)
if (device.status?.o2) setHasO2(true)
if (device.status?.co) setHasCo(true)
if (device.status?.co2) setHasCo2(true)
if (device.status?.no2) setHasNo2(true)
newDevices.push(Device.create(device))
})
setDevices(newDevices)
@ -277,15 +326,40 @@ export default function Devices() {
setLimit(event.target.value);
};
const prependToUrl = (prependage: number | string) => {
const basePath = location.pathname.replace(/\/$/, "");
return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1"));
};
// const prependToUrl = (prependage: number | string) => {
// const basePath = location.pathname.replace(/\/$/, "");
// return(`/${prependage}/${basePath}`.replace(/\/{2,}/g, "/").replace(/(\/[^/]+)\/\1+/g, "$1"));
// };
function insertGroupContext(groupID: string, deviceID: string): string {
const path = location.pathname
const segments = path.split('/').filter(Boolean);
const deviceIndex = segments.findIndex(segment => segment === 'devices');
if (deviceIndex === -1) {
throw new Error('Path does not contain "devices"');
}
// Build the new segments
const newSegments = [
...segments.slice(0, deviceIndex),
'groups',
groupID,
...segments.slice(deviceIndex),
deviceID
];
return '/' + newSegments.join('/');
}
// useEffect(() => {
// console.log(devices)
// }, [devices])
const toDevice = (device: Device) => {
let url = prependToUrl(getGroup() ? "groups/" + getGroup().id() : "")
url = url + "/" + device.id()
navigate(url, { replace: true, state: {device: device} })
let url = getGroup() ? insertGroupContext(getGroup().id().toString(), device.id().toString()) : ""
if (url.length < 1) url = device.id().toString()
navigate(url, { state: {device: device} })
};
const handleTabChange = (_event: React.SyntheticEvent, newValue: string) => {
@ -293,10 +367,6 @@ export default function Devices() {
if (newValue === "never") openGroupSettings(undefined, "add");
};
useEffect(() => {
console.log(devices)
}, [devices])
const columns = (): Column<Device>[] => {
let columns: Column<Device>[] = [
{
@ -338,12 +408,13 @@ export default function Devices() {
title: "Description",
render: (device: Device) => {
const description = device.settings?.description ?? ""
let size = (hasCo2 && !groupGas) ? 26 : 36
return (
<Box className={classes.descriptionCellContainer}>
<Tooltip title={device.settings?.description}>
<Box className={classes.descriptionCellContainer} width={size*6}>
<Tooltip title={description}>
<span>
{description.length > 36
? description.substring(0, 36) + "..."
{description.length > size
? description.substring(0, size) + "..."
: description}
</span>
</Tooltip>
@ -396,7 +467,95 @@ export default function Devices() {
if (device.status.sen5x?.temperature) {
return (
<Box sx={{ marginLeft: 1 }}>
<StatusSen5x device={device} />
<StatusSen5x device={device} noDust={separateDust} />
</Box>
)
} else {
return (
<></>
)
}
}
})
if (separateDust) columns.push({
title: "Dust",
render: (device: Device) => {
if (device.status.sen5x?.temperature) {
return (
<Box sx={{ marginLeft: 1 }}>
<StatusDust device={device} />
</Box>
)
} else {
return (
<></>
)
}
}
})
}
if (hasCo) {
columns.push({
title: groupGas ? "Gas" : "CO",
render: (device: Device) => {
if (device.status.co) {
return (
<Box sx={{ marginLeft: 1 }}>
<StatusGas device={device} gasType={groupGas ? "all" : "co"}/>
</Box>
)
} else {
return (
<></>
)
}
}
})
}
if (hasCo2 && !groupGas) {
columns.push({
title: "CO2",
render: (device: Device) => {
if (device.status.co2) {
return (
<Box sx={{ marginLeft: 1 }}>
<StatusGas device={device} gasType={"co2"}/>
</Box>
)
} else {
return (
<></>
)
}
}
})
}
if (hasNo2 && !groupGas) {
columns.push({
title: "NO2",
render: (device: Device) => {
if (device.status.no2) {
return (
<Box sx={{ marginLeft: 1 }}>
<StatusGas device={device} gasType={"no2"}/>
</Box>
)
} else {
return (
<></>
)
}
}
})
}
if (hasO2 && !groupGas) {
columns.push({
title: "O2",
render: (device: Device) => {
if (device.status.o2) {
return (
<Box sx={{ marginLeft: 1 }}>
<StatusGas device={device} gasType={"o2"}/>
</Box>
)
} else {
@ -499,6 +658,51 @@ export default function Devices() {
)
}
const preferencesButton = () => {
return (
<IconButton onClick={(event) => setPreferencesAnchor(event.currentTarget)}>
<Tune />
</IconButton>
)
}
const preferencesMenu = () => {
return (
<Menu
id="userMenu"
anchorEl={preferencesAnchor}
open={preferencesAnchor !== null}
onClose={() => setPreferencesAnchor(null)}
disableAutoFocusItem>
<MenuItem
onClick={() => setSeparateDust(!separateDust)}
sx={{ marginLeft: -0.75 }}
dense
>
<ListItemIcon>
<Checkbox
checked={!separateDust}
/>
</ListItemIcon>
<ListItemText primary={"Group Dust"} />
</MenuItem>
<MenuItem
onClick={() => setGroupGas(!groupGas)}
sx={{ marginLeft: -0.75 }}
dense
>
<ListItemIcon>
<Checkbox
checked={groupGas}
/>
</ListItemIcon>
<ListItemText primary={"Group Gas"} />
</MenuItem>
</Menu>
)
}
return(
<PageContainer padding={isMobile ? 0 : 2}>
<Box
@ -548,6 +752,42 @@ export default function Devices() {
setOrder={setOrder}
orderBy={orderBy}
setOrderBy={setOrderBy}
endTitleElement={preferencesButton}
loadMore={()=>{
let currentRows = cloneDeep(devices)
deviceAPI.list(
limit,
currentRows.length,
order,
orderBy,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
getKeys(),
getTypes(),
fieldContains,
search,
as
).then(resp => {
let newDevices: Device[] = []
resp.data.devices.forEach(device => {
if (device.status?.plenum?.temperature) setHasPlenums(true)
if (device.status?.sen5x?.temperature) setHasSen5x(true)
if (device.status?.o2) setHasO2(true)
if (device.status?.co) setHasCo(true)
if (device.status?.co2) setHasCo2(true)
if (device.status?.no2) setHasNo2(true)
newDevices.push(Device.create(device))
})
setDevices(currentRows.concat(newDevices))
}).catch(err => {
})
}}
/>
<ProvisionDevice
isOpen={isProvisionDialogOpen}
@ -566,6 +806,7 @@ export default function Devices() {
removeGroupCallback={removeGroupCallback}
addGroupCallback={addGroupCallback}
/>
{preferencesMenu()}
</PageContainer>
)
}

View file

@ -339,6 +339,7 @@ export default function Gate(props: Props) {
}}
style={{ height: 150, width: "100%", margin: -15 }}>
<Grid
width="100%"
container
direction="row"
spacing={2}

View file

@ -87,6 +87,11 @@ export function dragerGasDongle(subtype: number = 0): ComponentTypeExtension {
key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_H2S,
value: "DRAGER_GAS_DONGLE_SUBTYPE_H2S",
friendlyName: "Drager Gas H2S"
},
{
key: quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_LEL,
value: "DRAGER_GAS_DONGLE_SUBTYPE_LEL",
friendlyName: "Drager Gas LEL"
}
],
friendlyName: "Drager Gas",
@ -162,7 +167,8 @@ export function dragerGasDongle(subtype: number = 0): ComponentTypeExtension {
lines: [],
average: []
};
if (subtype === quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_O2) {
//sets the value as a percent for the chart data
if (subtype === quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_O2 || subtype === quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_LEL) {
let firstTime: Moment | undefined;
let lastTime: Moment | undefined;
let valMap: Map<number, number[]> = new Map<number, number[]>();

View file

@ -45,6 +45,11 @@ export function Pressure(subtype: number = 0): ComponentTypeExtension {
key: quack.PressureSubtype.PRESSURE_SUBTYPE_FROG,
value: "PRESSURE_SUBTYPE_FROG",
friendlyName: "Pressure (Frog)"
} as Subtype,
{
key: quack.PressureSubtype.PRESSURE_SUBTYPE_DIFFERENTIAL_PRESSURE,
value: "PRESSURE_SUBTYPE_DIFFERENTIAL_PRESSURE",
friendlyName: "Pressure Differential"
} as Subtype
],
friendlyName: "Pressure",

View file

@ -73,6 +73,8 @@ function getTooltip(platform: pond.DevicePlatform): string {
case pond.DevicePlatform.DEVICE_PLATFORM_PHOTON ||
pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_S3:
return "Communicates via Wi-Fi";
case pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE:
return "Communicates view Ethernet"
default:
return "";
}

View file

@ -4,7 +4,9 @@ import {
SignalCellular4Bar,
SignalWifi4Bar,
SignalCellularOff,
SignalWifiOff
SignalWifiOff,
Cable,
DoNotDisturb
} from "@mui/icons-material";
import { pond } from "protobuf-ts/pond";
@ -98,6 +100,15 @@ const V2_Wifi_Blue: DevicePlatformDescriber = {
platformName: "v2wifiblue"
};
const V2_Ethernet_Blue: DevicePlatformDescriber = {
platform: pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE,
label: "V2 Ethernet Blue",
description: "Communicates via ethernet",
icon: <Cable style={{ color: "#fff" }} />,
offlineIcon: <DoNotDisturb style={{ color: "#fff" }} />,
platformName: "v2ethernetblue"
};
const map = new Map<pond.DevicePlatform, DevicePlatformDescriber>([
[pond.DevicePlatform.DEVICE_PLATFORM_INVALID, Default],
[pond.DevicePlatform.DEVICE_PLATFORM_PHOTON, Photon],
@ -107,7 +118,8 @@ const map = new Map<pond.DevicePlatform, DevicePlatformDescriber>([
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLACK, V2_Cell_Black],
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_GREEN, V2_Cell_Green],
[pond.DevicePlatform.DEVICE_PLATFORM_V2_CELLULAR_BLUE, V2_Cell_Blue],
[pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE, V2_Wifi_Blue]
[pond.DevicePlatform.DEVICE_PLATFORM_V2_WIFI_BLUE, V2_Wifi_Blue],
[pond.DevicePlatform.DEVICE_PLATFORM_V2_ETHERNET_BLUE, V2_Ethernet_Blue]
]);
export function getDevicePlatformDescriber(platform: pond.DevicePlatform): DevicePlatformDescriber {

View file

@ -334,6 +334,7 @@ export class MeasurementDescriber {
this.details.unit = "mV";
break;
case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_O2:
case quack.DragerGasDongleSubtype.DRAGER_GAS_DONGLE_SUBTYPE_LEL:
this.details.colour = blue["400"];
this.details.decimals = 1;
this.details.unit = "%";
@ -342,7 +343,7 @@ export class MeasurementDescriber {
mode: "toStored" | "toDisplay"
): number => {
if (mode === "toStored") {
//for O2 dragers the display is in percentage so convert the percent to ppm
//for O2/LEL dragers the display is in percentage so convert the percent to ppm
return value * 10000;
}
return value / 10000;

View file

@ -191,6 +191,38 @@ export const BindaptV2Automate: DeviceProductDescriber = {
// ])
};
export const StreamlineMonitor: DeviceProductDescriber = {
product: pond.DeviceProduct.DEVICE_PRODUCT_STREAMLINE_MONITOR,
label: "Streamline Monitor",
icon: (_, theme?: "light" | "dark") => {
return theme === "light" ? BindaptPlusDarkIcon : BindaptPlusLightIcon;
},
view: "controller",
tabs: getBindaptTabs(),
availability: BindaptV2MonitorAvailability
//TODO not sure what the default address map for these should be yet
// defaultAddressMap: new Map<string, string[]>([
// ["plenum", ["9-2-64"]],
// ["heatersFans", ["3-1-512"]] //refers to heaters and fans
// ])
};
export const StreamlineAutomate: DeviceProductDescriber = {
product: pond.DeviceProduct.DEVICE_PRODUCT_STREAMLINE_AUTOMATE,
label: "Streamline Automate",
icon: (_, theme?: "light" | "dark") => {
return theme === "light" ? BindaptPlusDarkIcon : BindaptPlusLightIcon;
},
view: "controller",
tabs: getBindaptTabs(),
availability: BindaptV2AutomateAvailability
//TODO not sure what the default address map for these should be yet
// defaultAddressMap: new Map<string, string[]>([
// ["plenum", ["9-2-64"]],
// ["heatersFans", ["3-1-512"]] //refers to heaters and fans
// ])
};
export function IsBindaptDevice(product?: pond.DeviceProduct): boolean {
const bindaptProducts: pond.DeviceProduct[] = [
pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_MINI,

View file

@ -11,7 +11,9 @@ import {
BindaptV2Automate,
BinHalo,
BinMonitor,
BinUltimate
BinUltimate,
StreamlineMonitor,
StreamlineAutomate
} from "./Bindapt/BindaptDescriber";
import { NexusST } from "./Nexus/NexusDescriber";
import { MiPCAV2, OmniAir } from "./OmniAir/OmniAirDescriber";
@ -19,6 +21,7 @@ import { ConfigurablePin } from "pbHelpers/AddressTypes";
import { useTheme } from "@mui/material";
import { Device } from "models";
import { or } from "utils";
import { MiVent, MiVentV2 } from "./MiVent/MiVentDescriber";
export type ProductTab = "components" | "presets";
@ -87,7 +90,11 @@ const DEVICE_PRODUCT_MAP = new Map<pond.DeviceProduct, DeviceProductDescriber>([
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_MOD, BindaptPlusMod],
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_PLUS_PRO_MOD, BindaptPlusProMod],
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_MONITOR, BindaptV2Monitor],
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_AUTOMATE, BindaptV2Automate]
[pond.DeviceProduct.DEVICE_PRODUCT_BINDAPT_V2_AUTOMATE, BindaptV2Automate],
[pond.DeviceProduct.DEVICE_PRODUCT_MIVENT, MiVent],
[pond.DeviceProduct.DEVICE_PRODUCT_MIVENT_V2, MiVentV2],
[pond.DeviceProduct.DEVICE_PRODUCT_STREAMLINE_MONITOR, StreamlineMonitor],
[pond.DeviceProduct.DEVICE_PRODUCT_STREAMLINE_AUTOMATE, StreamlineAutomate]
]);
export function ListDeviceProductDescribers(): DeviceProductDescriber[] {

View file

@ -0,0 +1,61 @@
import { ConfigurablePin } from "pbHelpers/AddressTypes";
import { DeviceAvailabilityMap, DevicePositions } from "pbHelpers/DeviceAvailability";
import { quack } from "protobuf-ts/pond";
const MiVentV1Pins: ConfigurablePin[] = [
{ address: 4, label: "1" },
{ address: 8, label: "2" },
{ address: 16, label: "3" },
{ address: 512, label: "C1" },
{ address: 1024, label: "C2" }
];
export const MiVentV1Availability: DeviceAvailabilityMap = new Map<
quack.AddressType,
DevicePositions
>([
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, MiVentV1Pins],
[
quack.AddressType.ADDRESS_TYPE_I2C,
new Map<quack.ComponentType, number[]>([
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x2a]]
])
],
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
]);
/*---V2 Stuff starts here---*/
const MiVentV2Pins: ConfigurablePin[] = [
{ address: 1, label: "1" },
{ address: 2, label: "2" },
{ address: 4, label: "3" },
{ address: 256, label: "L1" },
{ address: 512, label: "L2" }
];
export const MiVentV2Availability: DeviceAvailabilityMap = new Map<
quack.AddressType,
DevicePositions
>([
[quack.AddressType.ADDRESS_TYPE_INVALID, []],
[quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY, MiVentV2Pins],
[
quack.AddressType.ADDRESS_TYPE_I2C,
new Map<quack.ComponentType, number[]>([
[quack.ComponentType.COMPONENT_TYPE_PRESSURE, [0x18]],
[quack.ComponentType.COMPONENT_TYPE_DHT, [0x40]],
[quack.ComponentType.COMPONENT_TYPE_SEN5X, [0x69]],
[quack.ComponentType.COMPONENT_TYPE_AIRFLOW, [0x2a]]
])
],
[quack.AddressType.ADDRESS_TYPE_POWER, [0]],
[quack.AddressType.ADDRESS_TYPE_GPS, [0]],
[quack.AddressType.ADDRESS_TYPE_MODEM, [0]]
]);

View file

@ -0,0 +1,29 @@
import { DeviceProductDescriber } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond";
import DeviceDarkIcon from "assets/products/Ag/device 1.png";
import DeviceLightIcon from "assets/products/Ag/device 2.png";
import { MiVentV1Availability, MiVentV2Availability } from "./MiVentAvailability";
export const MiVent: DeviceProductDescriber = {
product: pond.DeviceProduct.DEVICE_PRODUCT_MIVENT,
label: "MiVent V1",
icon: (_, theme?: "light" | "dark") => {
return theme === "light" ? DeviceDarkIcon : DeviceLightIcon;
},
view: "controller",
tabs: ["components"],
availability: MiVentV1Availability
};
/*---V2 Stuff starts here---*/
export const MiVentV2: DeviceProductDescriber = {
product: pond.DeviceProduct.DEVICE_PRODUCT_MIVENT_V2,
label: "MiVent V2",
icon: (_, theme?: "light" | "dark") => {
return theme === "light" ? DeviceDarkIcon : DeviceLightIcon;
},
view: "controller",
tabs: ["components"],
availability: MiVentV2Availability
};

View file

@ -1,14 +1,9 @@
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
// import { useAuth } from "hooks";
import moment from "moment";
import { createContext, PropsWithChildren, useContext } from "react";
// import BillingProvider from "./billing";
// import GitlabProvider from "./gitlab";
import PondProvider from "./pond/pond";
import { useAuth0 } from "@auth0/auth0-react";
import SnackbarProvider from "./Snackbar";
// import SecurityProvider from "./security";
// import { useAuth0 } from "@auth0/auth0-react";
interface IHTTPContext {
get: <T>(url: string, spreadOptions?: AxiosRequestConfig) => Promise<AxiosResponse<T>>;
@ -78,9 +73,7 @@ export default function HTTPProvider(props: Props) {
}
function get<T>(url: string, spreadOptions?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
if (isTokenExpired(token)) {
loginWithRedirect()
}
if (isTokenExpired(token)) loginWithRedirect()
return axios.get(url, {...defaultOptions(), ...spreadOptions});
}

View file

@ -251,6 +251,47 @@ export default function TeamList() {
isLoading={loading}
renderGutter={isMobile ? renderGutter : undefined}
hideKeys
loadMore={() => {
let current = cloneDeep(teams)
setLoading(true);
let newTeamPerms: Map<string | number, pond.Permission[]> = new Map();
let newTeamPrefs: Map<string | number, pond.TeamPreferences> = new Map();
let viewAs: string | undefined = as
if (as === team.key()) {
viewAs = undefined
}
teamAPI
.listTeams(limit, current.length, undefined, "name", undefined, undefined, " ", search)
.then(resp => {
setTotal(resp.data.total ? resp.data.total : 0);
let newTeams: Team[] = []
resp.data.teams.forEach(team => {
let u = viewAs ? viewAs : user.id();
let t = Team.create(team)
if (team.settings && u !== "") {
userAPI
.getUser(u, teamScope(team.settings.key))
.then(resp => {
newTeamPerms.set(t.key(), resp.permissions)
newTeamPrefs.set(t.key(), resp.preferences)
t.preferences = cloneDeep(resp.preferences)
t.permissions = cloneDeep(resp.permissions)
}).catch(() => {
snackbar.error("Error loading teams.")
})
.finally(() => {
setTeamPerms(newTeamPerms);
setTeamPrefs(newTeamPrefs);
});
}
newTeams.push(t)
});
setTeams(current.concat(newTeams));
})
.finally(() => {
setLoading(false);
});
}}
/>
<TeamSettings
teamDialogOpen={teamDialogOpen}

View file

@ -17,6 +17,20 @@ const baseTheme: ThemeOptions = {
paper: '#f5f5f5',
},
},
typography: {
fontFamily: [
"Open Sans",
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
'"Helvetica Neue"',
"Arial",
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"'
].join(",")
}
// Add other options if needed
};

View file

@ -73,14 +73,19 @@ export default function AccessObject(props: Props) {
const [teamsTotal, setTeamsTotal] = useState<number>(0);
const [devicesOffset, setDevicesOffset] = useState<number>(0);
const [devicesTotal, setDevicesTotal] = useState<number>(0);
const [prevAs, setPrevAs] = useState("")
const close = () => {
const close = (restoreAs?: boolean) => {
if(restoreAs){
dispatch({ key: "as", value: prevAs });
}
closeDialogCallback();
};
const getAccess = (key: number | string, name: string, url: string, useImitation = false) => {
user.settings.useTeam = false;
dispatch({ key: "as", value: "" });
//dispatch({ key: "as", value: "" });
userAPI.updateUser(user.id(), user.protobuf()).then(() => {
info("Will no longer view as team by default");
permissionAPI
@ -258,6 +263,12 @@ export default function AccessObject(props: Props) {
useEffect(() => {
if (isOpen && (prevSearchValue !== searchValue || prevKind !== kind || isOpen !== prevOpen)) {
//if the user is viewing as a team record who it was
if(as){
setPrevAs(as)
}
//change as so the user is viewing as themself
dispatch({ key: "as", value: "" });
load();
}
}, [
@ -350,12 +361,12 @@ export default function AccessObject(props: Props) {
return (
<ResponsiveDialog
open={isOpen}
onClose={close}
onClose={() => close(true)}
aria-labelledby="access-object-dialog"
fullScreen>
<AppBar position="sticky">
<Toolbar>
<IconButton edge="start" color="inherit" onClick={close} aria-label="close">
<IconButton edge="start" color="inherit" onClick={() => close(true)} aria-label="close">
<CloseIcon />
</IconButton>
<Typography variant="h6" noWrap>