-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
412 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
src/renderer/components/pages/Config/pages/ArmPresetsConfig/ArmJointInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { LabeledInput } from '@/renderer/components/common/LabeledInput'; | ||
import React, { useCallback, useState } from 'react'; | ||
|
||
interface ArmJointInputProps { | ||
onChange: (event: React.ChangeEvent<HTMLInputElement>, id: number) => void; | ||
value: number; | ||
id: number; | ||
min: number; | ||
max: number; | ||
} | ||
|
||
const ArmJointInput = ({ | ||
onChange, | ||
value, | ||
id, | ||
min, | ||
max, | ||
}: ArmJointInputProps) => { | ||
const [inputValue, setInputValue] = useState(value.toString()); | ||
const [error, setError] = useState(false); | ||
|
||
const onInputChange = useCallback( | ||
(event: React.ChangeEvent<HTMLInputElement>) => { | ||
const jointValue = parseInt(event.target.value); | ||
setInputValue(event.target.value); | ||
if (jointValue >= min && jointValue <= max) { | ||
onChange(event, id); | ||
setError(false); | ||
} else { | ||
setError(true); | ||
} | ||
}, | ||
[id, max, min, onChange] | ||
); | ||
|
||
return ( | ||
<div> | ||
<LabeledInput | ||
label={`Joint ${id + 1}`} | ||
value={inputValue} | ||
type="number" | ||
onChange={onInputChange} | ||
/> | ||
{error && ( | ||
<div> | ||
Value must be between {min} and {max} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default ArmJointInput; |
136 changes: 136 additions & 0 deletions
136
src/renderer/components/pages/Config/pages/ArmPresetsConfig/ArmPreset.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import React, { useCallback } from 'react'; | ||
import { | ||
ArmPreset, | ||
armPresetsSlice, | ||
} from '@/renderer/store/modules/armPresets'; | ||
import { styled } from '@/renderer/globalStyles/styled'; | ||
import { LabeledInput } from '@/renderer/components/common/LabeledInput'; | ||
import { useDispatch } from 'react-redux'; | ||
import { FaTimes } from 'react-icons/fa'; | ||
import ArmJointInput from './ArmJointInput'; | ||
|
||
interface ArmPresetProps { | ||
preset: ArmPreset; | ||
} | ||
|
||
//Temporary UI limit for each joint value. | ||
interface JointBoundaries { | ||
min: number; | ||
max: number; | ||
} | ||
|
||
const jointBoundaries: JointBoundaries[] = [ | ||
{ min: 0, max: 360 }, //Joint 1 | ||
{ min: 100, max: 260 }, //Joint 2 | ||
{ min: 95, max: 260 }, //Joint 3 | ||
{ min: 0, max: 360 }, //Joint 4 | ||
{ min: 100, max: 250 }, //Joint 5 | ||
{ min: 0, max: 360 }, //Joint 6 | ||
]; | ||
|
||
const Card = styled.div` | ||
background-color: ${({ theme }) => theme.colors.darkerBackground}; | ||
border-radius: 4px; | ||
padding: 16px; | ||
margin: 8px; | ||
display: flex; | ||
min-width: 300px; | ||
flex-direction: column; | ||
justify-content: flex-start; | ||
align-items: flex-start; | ||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5); | ||
transition: box-shadow 0.2s ease-in-out; | ||
`; | ||
|
||
const HeaderRow = styled.div` | ||
display: flex; | ||
flex-direction: row; | ||
justify-content: space-between; | ||
align-items: center; | ||
width: 100%; | ||
`; | ||
|
||
const DeleteButton = styled.a` | ||
border: none; | ||
border-radius: 4px; | ||
padding: 4px 8px; | ||
margin-top: 8px; | ||
cursor: pointer; | ||
transition: background-color 0.2s ease-in-out; | ||
&:hover { | ||
color: ${({ theme }) => theme.colors.danger}; | ||
} | ||
`; | ||
|
||
const ArmPreset = ({ preset }: ArmPresetProps) => { | ||
const dispatch = useDispatch(); | ||
|
||
const onJointChange = useCallback( | ||
(event: React.ChangeEvent<HTMLInputElement>, id: number) => { | ||
const jointValue = parseInt(event.target.value); | ||
// Update the preset in the store with the new joint value. | ||
dispatch( | ||
armPresetsSlice.actions.updatePreset({ | ||
...preset, | ||
positions: [ | ||
...preset.positions.slice(0, id), | ||
jointValue, | ||
...preset.positions.slice(id + 1), | ||
], | ||
}) | ||
); | ||
}, | ||
[dispatch, preset] | ||
); | ||
|
||
const onNameChange = useCallback( | ||
(event: React.ChangeEvent<HTMLInputElement>) => { | ||
// Update the preset in the store with the new name. | ||
dispatch( | ||
armPresetsSlice.actions.updatePreset({ | ||
...preset, | ||
name: event.target.value, | ||
}) | ||
); | ||
}, | ||
[dispatch, preset] | ||
); | ||
|
||
const onDelete = useCallback( | ||
(id: string) => { | ||
// Delete the preset from the store. | ||
dispatch(armPresetsSlice.actions.removePreset(id)); | ||
}, | ||
[dispatch] | ||
); | ||
|
||
return ( | ||
<Card> | ||
<HeaderRow> | ||
<h3>{preset.name}</h3> | ||
<DeleteButton onClick={() => onDelete(preset.id)}> | ||
<FaTimes /> | ||
</DeleteButton> | ||
</HeaderRow> | ||
<LabeledInput | ||
label="Preset Name" | ||
value={preset.name} | ||
type="text" | ||
onChange={onNameChange} | ||
/> | ||
{preset.positions.map((joint, index) => ( | ||
<div key={index}> | ||
<ArmJointInput | ||
value={joint} | ||
id={index} | ||
onChange={onJointChange} | ||
min={jointBoundaries[index].min} | ||
max={jointBoundaries[index].max} | ||
/> | ||
</div> | ||
))} | ||
</Card> | ||
); | ||
}; | ||
|
||
export default ArmPreset; |
92 changes: 92 additions & 0 deletions
92
src/renderer/components/pages/Config/pages/ArmPresetsConfig/ArmPresetsConfig.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { Button } from '@/renderer/components/common/Button'; | ||
import { Select } from '@/renderer/components/common/Select'; | ||
import { styled } from '@/renderer/globalStyles/styled'; | ||
import useArmJointPositions from '@/renderer/hooks/useArmJointPositions'; | ||
import { | ||
armPresetsSlice, | ||
selectAllPresets, | ||
selectSelectedPreset, | ||
} from '@/renderer/store/modules/armPresets'; | ||
import { round } from 'lodash'; | ||
import { nanoid } from 'nanoid'; | ||
import React, { useCallback } from 'react'; | ||
import { FaPlus } from 'react-icons/fa'; | ||
import { useDispatch, useSelector } from 'react-redux'; | ||
import { SectionTitle } from '../../styles'; | ||
import ArmPreset from './ArmPreset'; | ||
|
||
const PresetsContainer = styled.div` | ||
display: flex; | ||
flex-direction: row; | ||
justify-content: flex-start; | ||
align-items: flex-start; | ||
width: 100%; | ||
flex-wrap: wrap; | ||
`; | ||
|
||
const ArmPresetsConfig = () => { | ||
const armPresets = useSelector(selectAllPresets); | ||
const selectedPreset = useSelector(selectSelectedPreset); | ||
const dispatch = useDispatch(); | ||
const jointPositions = useArmJointPositions() ?? []; | ||
|
||
const addPreset = useCallback(() => { | ||
dispatch( | ||
armPresetsSlice.actions.addPreset({ | ||
id: nanoid(), | ||
name: 'New Preset', | ||
positions: [180, 180, 180, 180, 180, 180], | ||
}) | ||
); | ||
}, [dispatch]); | ||
|
||
const onPresetSelect = useCallback( | ||
(e: React.ChangeEvent<HTMLSelectElement>) => { | ||
dispatch(armPresetsSlice.actions.selectPreset(e.target.value)); | ||
}, | ||
[dispatch] | ||
); | ||
|
||
return ( | ||
armPresets && | ||
selectedPreset && ( | ||
<> | ||
<SectionTitle>Selected Preset</SectionTitle> | ||
<Select | ||
options={armPresets.map((preset) => ({ | ||
key: preset.id, | ||
content: preset.name, | ||
value: preset.id, | ||
}))} | ||
value={selectedPreset.id} | ||
onChange={onPresetSelect} | ||
/> | ||
{jointPositions.length > 0 && ( | ||
<> | ||
<SectionTitle>Current positions</SectionTitle> | ||
<div> | ||
{jointPositions.map((position, index) => ( | ||
<div key={index}> | ||
Joint {index + 1}: {round(position, 0)} | ||
</div> | ||
))} | ||
</div> | ||
</> | ||
)} | ||
|
||
<SectionTitle>Presets</SectionTitle> | ||
<Button onClick={addPreset}> | ||
<FaPlus /> | ||
<span style={{ marginLeft: '0.2rem' }}>Add Preset</span> | ||
</Button> | ||
<PresetsContainer> | ||
{armPresets.map((preset) => ( | ||
<ArmPreset key={preset.id} preset={preset} /> | ||
))} | ||
</PresetsContainer> | ||
</> | ||
) | ||
); | ||
}; | ||
|
||
export default ArmPresetsConfig; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useCallback, useState } from 'react'; | ||
import { TopicOptions } from '../utils/ros/roslib-ts-client/@types'; | ||
import { useRosSubscribeNoData } from './useRosSubscribe'; | ||
|
||
const jointPositionTopic: TopicOptions = { | ||
name: 'ovis/arm/out/joint_position', | ||
messageType: 'ovis_msgs/OvisJointPosition', | ||
}; | ||
|
||
interface JointPositionMsg { | ||
joint_positions: number[]; | ||
} | ||
|
||
const useArmJointPositions = () => { | ||
const [jointPositions, setJointPositions] = useState<number[]>(); | ||
|
||
useRosSubscribeNoData<JointPositionMsg>( | ||
jointPositionTopic, | ||
useCallback((message) => { | ||
setJointPositions(message.joint_positions); | ||
}, []) | ||
); | ||
|
||
return jointPositions; | ||
}; | ||
|
||
export default useArmJointPositions; |
Oops, something went wrong.