Skip to content

Commit

Permalink
Update header bar : gpio and exploration timer config (#179)
Browse files Browse the repository at this point in the history
* add exploration timer

* fix lint

* re fix lint

* move gpio config to header tab

* try fix pr comment

* try fix pr comment

* debug

* clean up
  • Loading branch information
amDeimos666 authored Jun 12, 2023
1 parent e490156 commit da5381a
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 78 deletions.
42 changes: 6 additions & 36 deletions src/renderer/components/BatteryStatus/BatteryStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import BatteryGauge from 'react-battery-gauge';
import React, { memo } from 'react';
import { styled } from '@/renderer/globalStyles/styled';
import Popup from 'reactjs-popup';
import {
StyledPopup,
StyledPopupContainer,
} from '@/renderer/components/styles';
import BatteryDetailsPopup from './BatteryDetailsPopup';
import { defaultTheme } from '@/renderer/globalStyles/themes/defaultTheme';
import useBatteryInfo from '@/renderer/hooks/useBatteryInfo';
Expand Down Expand Up @@ -36,7 +39,7 @@ const BatteryStatus = ({ name, topicName }: BatteryStatusProps) => {
return (
<StyledPopup
trigger={
<Container>
<StyledPopupContainer>
<PercentageText>{name}</PercentageText>
<PercentageText>{batteryInfo.percentage.toFixed(0)}%</PercentageText>
<BatteryGauge
Expand All @@ -45,7 +48,7 @@ const BatteryStatus = ({ name, topicName }: BatteryStatusProps) => {
aspectRatio={0.42}
customization={customization}
/>
</Container>
</StyledPopupContainer>
}
on="click"
position="bottom center"
Expand All @@ -61,43 +64,10 @@ const BatteryStatus = ({ name, topicName }: BatteryStatusProps) => {
);
};

const Container = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
&:hover {
cursor: pointer;
}
`;

const PercentageText = styled.div`
font-size: 12px;
margin-right: 5px;
font-weight: bold;
`;

const StyledPopup = styled(Popup)`
@keyframes anvil {
0% {
transform: scale(1) translateY(0px);
opacity: 0;
box-shadow: 0 0 0 rgba(241, 241, 241, 0);
}
1% {
transform: scale(0.96) translateY(10px);
opacity: 0;
box-shadow: 0 0 0 rgba(241, 241, 241, 0);
}
100% {
transform: scale(1) translateY(0px);
opacity: 1;
box-shadow: 0 0 500px rgba(241, 241, 241, 0);
}
}
&-content {
-webkit-animation: anvil 0.2s cubic-bezier(0.38, 0.1, 0.36, 0.9) forwards;
}
`;

export default memo(BatteryStatus);
46 changes: 46 additions & 0 deletions src/renderer/components/ExplorationStatus/ExplorationStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { FC, useState } from 'react';
import { styled } from '@/renderer/globalStyles/styled';
import { ExplorationTimer } from './ExplorationTimer';
import {
StyledPopup,
StyledPopupContent,
StyledPopupContainer,
} from '@/renderer/components/styles';
import { GoTelescope } from 'react-icons/go';

export const ExplorationStatus: FC = () => {
const [timerDisplay, setTimerDisplay] = useState('00:00');

const setTimerDisplayProps = (timerToDisplay: string) => {
setTimerDisplay(timerToDisplay);
};

const isShowTimerDisplay = () => {
return timerDisplay !== '00:00';
};

return (
<StyledPopup
trigger={
<StyledPopupContainer>
{isShowTimerDisplay() && timerDisplay}
<StyledGoTelescope />
</StyledPopupContainer>
}
on="click"
position="bottom center"
arrow={false}
repositionOnResize={true}
>
<StyledPopupContent>
<ExplorationTimer setTimerDisplayProps={setTimerDisplayProps} />
</StyledPopupContent>
</StyledPopup>
);
};

const StyledGoTelescope = styled(GoTelescope)`
height: 1.25em;
width: 1.25em;
margin-left: 0.5em;
`;
135 changes: 135 additions & 0 deletions src/renderer/components/ExplorationStatus/ExplorationTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Button } from '@/renderer/components/common/Button';
import { rosClient } from '@/renderer/utils/ros/rosClient';
import React, { useEffect, FC, useState, ChangeEvent } from 'react';
import { LabeledInput } from '@/renderer/components/common/LabeledInput';
import { log } from '@/renderer/logger';
import { styled } from '@/renderer/globalStyles/styled';

interface TimerDisplayProps {
setTimerDisplayProps: (timerDisplay: string) => void;
}

export const ExplorationTimer: FC<TimerDisplayProps> = ({
setTimerDisplayProps,
}) => {
const timeDisplayDefault = '00:00';

const [duration, setDuration] = useState(2);
const [timeRemaining, setTimeRemaining] = useState(0);
const [isTimerActive, setIsTimerActive] = useState(false);
const [timerDisplay, setTimerDisplay] = useState(timeDisplayDefault);
const [countDownDate, setCountDownDate] = useState(Date.now());

const updateDuration = (e: ChangeEvent<HTMLInputElement>) => {
setDuration(Number(e.target.value));
};

const startTimer = () => {
setIsTimerActive(false);
setCountDownDate(Date.now() + duration * 60 * 1000);
setIsTimerActive(true);
setRosExplorationTimer();
};

const stopTimer = () => {
setIsTimerActive(false);
setDuration(0);
setRosExplorationTimer();
};

useEffect(() => {
let interval: ReturnType<typeof setInterval> | undefined;
const intervalMs = 1000;
if (isTimerActive) {
interval = setInterval(() => {
setTimerDisplayProps(getTimeRemaining());
setTimerDisplay(getTimeRemaining());
setTimeRemaining(timeRemaining - intervalMs);
}, intervalMs);
} else if (!isTimerActive && timeRemaining !== 0) {
if (interval !== undefined) {
clearInterval(interval);
}
setTimerDisplay(timeDisplayDefault);
setTimerDisplayProps(timeDisplayDefault);
setTimerDisplay(timeDisplayDefault);
setTimeRemaining(0);
}

const getTimeRemaining = () => {
const total = countDownDate - Date.now();

const minutes = Math.floor((total % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((total % (1000 * 60)) / 1000);

const minutesDiplay =
minutes < 10 ? '0' + minutes.toString() : minutes.toString();
const secondsDiplay =
seconds < 10 ? '0' + seconds.toString() : seconds.toString();

if (total < 0) {
setIsTimerActive(false);
}
return `${minutesDiplay}:${secondsDiplay}`;
};
return () => clearInterval(interval);
}, [isTimerActive, countDownDate, timeRemaining, setTimerDisplayProps]);

const setRosExplorationTimer = () => {
rosClient
.callService(
{
name: `/start_exploration`,
},
{ timeout: duration * 60 }
)
.catch(log.error);
};

return (
<StyledDiv>
<div>
<LabeledInput
label="Duration of exploration (min)"
value={duration.toString()}
type="number"
onChange={updateDuration}
/>
<StyledDivDuration>
<Button
onClick={startTimer}
btnType="success"
style={{ maxWidth: '185px' }}
>
Start
</Button>
<Button
onClick={stopTimer}
btnType="danger"
style={{ maxWidth: '185px' }}
>
Stop
</Button>
</StyledDivDuration>
</div>
<StyledDivInfo>
<p>Time left</p>
<p>{timerDisplay}</p>
</StyledDivInfo>
</StyledDiv>
);
};

const StyledDiv = styled.div`
margin: 1em;
`;

const StyledDivDuration = styled.div`
display: flex;
column-gap: 20px;
`;

const StyledDivInfo = styled(StyledDivDuration)`
margin: 8px;
column-gap: 45px;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,6 @@ interface GpioPinProps {
gpioPin: GpioPinState;
}

const Card = styled.div`
background-color: ${({ theme }) => theme.colors.darkerBackground};
border-radius: 4px;
padding: 16px;
margin: 8px;
min-width: 150px;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5);
`;

export const GpioPin = ({ gpioPin }: GpioPinProps) => {
const dispatch = useDispatch();

Expand All @@ -43,7 +30,7 @@ export const GpioPin = ({ gpioPin }: GpioPinProps) => {

return (
<Card>
<h3 style={{ paddingBottom: '4px' }}>{gpioPin.name}</h3>
<SytledP>{gpioPin.name}</SytledP>
<IconButton
icon={<FaPowerOff />}
title={gpioPin.isOn ? 'Power Off' : 'Power On'}
Expand All @@ -57,3 +44,16 @@ export const GpioPin = ({ gpioPin }: GpioPinProps) => {
</Card>
);
};

const Card = styled.div`
padding: 8px;
margin: 4px;
min-width: 150px;
display: flex;
flex-direction: column;
justify-content: flex-start;
`;

const SytledP = styled.p`
margin-bottom: 0.25em;
`;
64 changes: 64 additions & 0 deletions src/renderer/components/GpioPinsStatus/GpioPinsStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { memo } from 'react';
import { StyledPopup } from '@/renderer/components/styles';
import { useSelector } from 'react-redux';
import { selectAllGpioPins } from '@/renderer/store/modules/gpioPins';
import { GpioPin } from './GpioPin';
import { styled } from '@/renderer/globalStyles/styled';
import { BsLightbulb } from 'react-icons/bs';
import { BsLightbulbOff } from 'react-icons/bs';

const GpioPinsStatus = () => {
const gpioPins = useSelector(selectAllGpioPins);
const isAGpioPinOn = gpioPins.find((gpioPin) => gpioPin.isOn)?.isOn;
return (
<StyledPopup
trigger={
<Container>
{isAGpioPinOn ? <StyledBsLightbulb /> : <StyledBsLightbulbOff />}
</Container>
}
on="click"
position="bottom center"
arrow={false}
repositionOnResize={true}
>
<StyledDiv>
{gpioPins.map((gpioPin) => (
<GpioPin key={gpioPin.id} gpioPin={gpioPin} />
))}
</StyledDiv>
</StyledPopup>
);
};

const Container = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
&:hover {
cursor: pointer;
}
`;

const StyledDiv = styled.div`
display: flex;
flex-direction: row;
align-items: flex-start;
background-color: ${({ theme }) => theme.colors.darkerBackground};
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5);
border-radius: 4px;
min-width: 150px;
`;

const StyledBsLightbulb = styled(BsLightbulb)`
height: 1.25em;
width: 1.25em;
`;

const StyledBsLightbulbOff = styled(BsLightbulbOff)`
height: 1.25em;
width: 1.25em;
`;

export default memo(GpioPinsStatus);
4 changes: 4 additions & 0 deletions src/renderer/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { NavLink } from 'react-router-dom';
import { selectDebugTabVisible } from '@/renderer/store/modules/debugTab';
import { useSelector } from 'react-redux';
import BatteryStatus from './BatteryStatus/BatteryStatus';
import GpioPinsStatus from './GpioPinsStatus/GpioPinsStatus';
import { ExplorationStatus } from './ExplorationStatus/ExplorationStatus';

interface NavLinkDefinition {
to: string;
Expand Down Expand Up @@ -47,6 +49,8 @@ export const Header: FC = () => {
))}
</LeftHeader>
<RightHeader>
<ExplorationStatus />
<GpioPinsStatus />
<BatteryStatus name="Motor" topicName="/vbus1" />
<BatteryStatus name="Logic" topicName="/vbus2" />
<StyledLogo src="assets/images/logo.png" />
Expand Down
Loading

0 comments on commit da5381a

Please sign in to comment.