Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ab7a235
feat(rtk): add saved coord restoration, initial commit, review required
zarcell Oct 7, 2025
2981b3c
chore: prettier and xo fixes
zarcell Oct 15, 2025
89c7528
fix: remove unused function
zarcell Oct 20, 2025
cfb4612
fix: format dialog text
zarcell Oct 20, 2025
acdb11d
fix: align slice dialog state with types
zarcell Nov 5, 2025
d65737e
fix(rtk): cleaner dialog, update accuracy, delete rtk from balcklist …
zarcell Nov 12, 2025
4cb8f3c
feat(rtk): improve saved coordinate management and restoration flow
zarcell Dec 3, 2025
e70b142
fix(rtk): remove temporary delete button
zarcell Dec 3, 2025
a84a034
fix(rtk): xo complaints
zarcell Dec 4, 2025
4d4f601
Merge branch 'dev' into rtk-savepos
zarcell Dec 4, 2025
3080a5e
chore(rtk): migrate RTK dialog to MUI v5 and add types
zarcell Dec 4, 2025
7acf399
fix(rtk): improve saved coordinate dialog appearance and title
zarcell Dec 4, 2025
840e3c5
feat(rtk): move coordinate restoration button to antenna position ind…
zarcell Dec 4, 2025
753944b
Merge branch 'dev' into rtk-savepos
zarcell Dec 4, 2025
d112af3
fix(rtk): hide restore button tooltip on focus
zarcell Dec 4, 2025
029ede3
feat(rtk): show preset title in save coordinate toast
zarcell Dec 4, 2025
80d9b3b
fix(rtk): fix coordinate check logic and cleanup fake data
zarcell Dec 8, 2025
fe8d3d0
fix(rtk): xo problems
zarcell Dec 8, 2025
0bb9b26
fix: accidental change
zarcell Jan 19, 2026
a0b953f
fix: cleaner placement instead of hacky margin
zarcell Jan 19, 2026
8f16dc0
fix: make SavedCoordinateItem a presentational component
zarcell Jan 19, 2026
fef235a
fix: use formatDistance from utils
zarcell Jan 19, 2026
60641a6
fix: sort alphabetically
zarcell Jan 19, 2026
868ad9f
fix: removed the redundant early return check
zarcell Jan 19, 2026
9b3b351
fix: use BackgroundHint
zarcell Jan 19, 2026
b9724dd
add: comment to explain why we dont need focus there
zarcell Jan 19, 2026
3a3d9e7
add: void operator to explicitly ignore promise in handleUseSaved
zarcell Jan 19, 2026
8807df2
fix: use general.action.cancel translation key
zarcell Jan 19, 2026
98e0b88
fix: sort
zarcell Jan 19, 2026
8ea1489
fix: prefer nullish coalescing
zarcell Jan 19, 2026
5efe0ae
refactor: keep the original logic
zarcell Jan 19, 2026
8335d86
fix: sort
zarcell Jan 19, 2026
627058c
fix: sort
zarcell Jan 19, 2026
aa7867b
fix: sorted
zarcell Jan 19, 2026
c77cc4b
fix: sorted
zarcell Jan 19, 2026
7a7e92c
fix: added optional chaining
zarcell Jan 19, 2026
9654fb3
fix: replaced the ternary with logical expression
zarcell Jan 19, 2026
75f852a
fix: simplified the prop assignment
zarcell Jan 19, 2026
744be83
fix: sort imports
zarcell Jan 20, 2026
4621a02
fix: use selector
zarcell Jan 23, 2026
a5cf8c1
refactor: flag check in hasValidFix
zarcell Jan 23, 2026
936770d
fix: remove undifined (never happens)
zarcell Jan 23, 2026
aa83d15
fix: sort
zarcell Jan 23, 2026
90d393e
fix: sort
zarcell Jan 23, 2026
92e5588
refactor: show RTK preset notifications even after dialog closes
zarcell Jan 23, 2026
db7f4d7
fix: nullish coalescing
zarcell Jan 23, 2026
935f4df
Update src/features/rtk/selectors.ts
zarcell Jan 23, 2026
c5edddf
fix: remove leftover function
zarcell Jan 23, 2026
622444d
refactor: rtk saved coordinate selector to return formatter
zarcell Jan 23, 2026
9e51aaa
add: icon to visualize clickability
zarcell Jan 23, 2026
1f58834
refactor: ECEF coordinate formatting into shared helper
zarcell Jan 23, 2026
570f2e7
chore: rename function
zarcell Jan 23, 2026
206f11c
chore: sort
zarcell Jan 23, 2026
99b0d5a
fix: optional property
zarcell Jan 23, 2026
0a7f820
fix: optional property
zarcell Jan 23, 2026
a35fbf7
fix: use nullish coalescing assignment
zarcell Jan 23, 2026
1929000
refactor: use isEqual from lodash
zarcell Jan 23, 2026
e4cf2e0
refactor: use isEqual here too
zarcell Jan 23, 2026
556dc18
fix: just let it be
zarcell Jan 23, 2026
d624797
fix: remove leftover function
zarcell Jan 23, 2026
5969c03
chore: sort
zarcell Jan 23, 2026
02f85e6
refactor: change to conjunction
zarcell Jan 23, 2026
29f847a
refactor: replaced the ternary with a logical expression
zarcell Jan 23, 2026
dbc8de0
refactor: use Coordinate3D
zarcell Jan 23, 2026
4deb7c9
chore: sort
zarcell Jan 23, 2026
4c4c2e8
chore: longer is better than larger :)
zarcell Jan 23, 2026
641b089
fix: update title
zarcell Jan 23, 2026
be2dd26
chore: remove a11y attributes as discussed earlier
isti115 Jan 25, 2026
db9da5d
refactor: nullish coalescing operator
zarcell Jan 25, 2026
91796cc
Merge branch 'rtk-savepos' of github.com:zarcell/live into rtk-savepos
zarcell Jan 25, 2026
0792630
chore: add changelog entry
isti115 Jan 25, 2026
f848288
chore: apply automatic formatting fixes
isti115 Jan 25, 2026
be774b8
fixup! chore: prettier and xo fixes
isti115 Feb 2, 2026
1e0db03
Merge branch 'dev' into rtk-savepos
zarcell Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fixed double event handling on the Logs panel of the UAV properties dialog.

- RTK base station coordinates can now be restored from positions stored during
earlier surveys.

## [2.12.1] - 2025-12-15

### Fixed
Expand Down
26 changes: 24 additions & 2 deletions src/features/rtk/AntennaPositionIndicator.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Restore from '@mui/icons-material/Restore';
import Box from '@mui/material/Box';
import IconButton from '@mui/material/IconButton';
import Typography from '@mui/material/Typography';
import PropTypes from 'prop-types';
Expand All @@ -15,15 +17,17 @@ import {
import { getAntennaInfoSummary } from './selectors';

const AntennaPositionIndicator = ({
hasSavedCoordinates,
onCopyAntennaPositionToClipboard,
onClick,
onShowSavedCoordinates,
position,
t,
}) => {
const hasAntennaPosition = Boolean(position);

return (
<>
<Box display='flex' alignItems='center' gap={0.5}>
<Tooltip content={t('antennaPositionIndicator.clickToToggle')}>
<Typography
variant='body2'
Expand All @@ -46,15 +50,33 @@ const AntennaPositionIndicator = ({
</IconButton>
</Tooltip>
)}
</>
{onShowSavedCoordinates && (
<Tooltip
content={t('antennaPositionIndicator.useSavedCoordinate')}
// Explicitly set trigger to 'mouseenter' only to prevent the tooltip
// from showing when the dialog closes and focus returns to this button.
trigger='mouseenter'
>
<IconButton
disabled={!hasSavedCoordinates}
size='large'
onClick={onShowSavedCoordinates}
>
<Restore />
</IconButton>
</Tooltip>
)}
</Box>
);
};

AntennaPositionIndicator.propTypes = {
// description: PropTypes.string,
hasSavedCoordinates: PropTypes.bool,
position: PropTypes.string,
onClick: PropTypes.func,
onCopyAntennaPositionToClipboard: PropTypes.func,
onShowSavedCoordinates: PropTypes.func,
t: PropTypes.func,
};

Expand Down
132 changes: 132 additions & 0 deletions src/features/rtk/RTKCoordinateRestorationDialog.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import formatDate from 'date-fns/format';
import PropTypes from 'prop-types';
import React from 'react';
import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';

import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';

import { BackgroundHint } from '@skybrush/mui-components';

import { closeCoordinateRestorationDialog } from '~/features/rtk/slice';
import {
getCoordinateRestorationDialogState,
getPreferredSavedRTKPositionFormatter,
getSavedCoordinatesForPreset,
} from '~/features/rtk/selectors';
import { useSavedCoordinateForPreset } from '~/features/rtk/actions';
import Download from '~/icons/Download';
import { formatDistance } from '~/utils/formatting';

const SavedCoordinateItem = ({ coordinate, coordinateFormatter, onClick }) => {
const { accuracy, savedAt } = coordinate;
const savedDateTime = formatDate(new Date(savedAt), 'yyyy-MM-dd HH:mm:ss');

const formattedPosition = coordinateFormatter(coordinate);

return (
<ListItem disablePadding>
<ListItemButton onClick={() => onClick(coordinate)}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
}}
>
<ListItemText
primary={formattedPosition}
secondary={`Accuracy: ${formatDistance(accuracy, 3)} • ${savedDateTime}`}
/>
<Download sx={{ marginLeft: 2 }} />
</Box>
</ListItemButton>
</ListItem>
);
};

const RTKCoordinateRestorationDialog = ({
coordinateFormatter,
dialog,
onClose,
onUseSaved,
savedCoordinates,
t,
}) => {
const { presetId } = dialog;

const handleUseSaved = (coordinate) => {
onClose(); // Close dialog first
void onUseSaved(presetId, coordinate); // Then start async operation
};

return (
<Dialog fullWidth open={dialog.open} maxWidth='sm' onClose={onClose}>
<DialogTitle>{t('RTKCoordinateRestorationDialog.title')}</DialogTitle>
<DialogContent>
<Box>
{savedCoordinates.length === 0 ? (
<BackgroundHint
text={t('RTKCoordinateRestorationDialog.noSavedCoordinates')}
/>
) : (
<List>
{savedCoordinates.map((coordinate) => (
<SavedCoordinateItem
key={coordinate.savedAt}
coordinate={coordinate}
coordinateFormatter={coordinateFormatter}
onClick={handleUseSaved}
/>
))}
</List>
)}
</Box>
</DialogContent>
<DialogActions>
<Button color='primary' onClick={onClose}>
{t('general.action.cancel')}
</Button>
</DialogActions>
</Dialog>
);
};

RTKCoordinateRestorationDialog.propTypes = {
coordinateFormatter: PropTypes.func,
dialog: PropTypes.object,
onClose: PropTypes.func,
onUseSaved: PropTypes.func,
savedCoordinates: PropTypes.array,
t: PropTypes.func,
};

export default connect(
(state) => {
const dialog = getCoordinateRestorationDialogState(state);

const coordinateFormatter = getPreferredSavedRTKPositionFormatter(state);

return {
dialog,
savedCoordinates: dialog.presetId
? getSavedCoordinatesForPreset(state, dialog.presetId)
: [],
coordinateFormatter,
};
},
{
onClose: closeCoordinateRestorationDialog,
onUseSaved: useSavedCoordinateForPreset,
}
)(withTranslation()(RTKCoordinateRestorationDialog));
35 changes: 29 additions & 6 deletions src/features/rtk/RTKCorrectionSourceSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { useAsync, useAsyncFn } from 'react-use';

import { resetRTKStatistics } from '~/features/rtk/slice';
import {
resetRTKStatistics,
setCurrentRTKPresetId,
} from '~/features/rtk/slice';
import messageHub from '~/message-hub';

const NULL_ID = '__null__';
Expand All @@ -19,7 +22,11 @@ const nullPreset = {
title: 'RTK disabled',
};

const RTKCorrectionSourceSelector = ({ onSourceChanged, t }) => {
const RTKCorrectionSourceSelector = ({
onSourceChanged,
setCurrentRTKPresetId,
t,
}) => {
const [selectedByUser, setSelectedByUser] = useState();
const [selectionState, getSelectionFromServer] = useAsyncFn(async () =>
messageHub.query.getSelectedRTKPresetId()
Expand All @@ -31,9 +38,13 @@ const RTKCorrectionSourceSelector = ({ onSourceChanged, t }) => {
);

const loading = presetsState.loading || selectionState.loading;
const hasError = presetsState.error || selectionState.error;
const hasError = presetsState.error;

const presets = presetsState.value ? presetsState.value : [];
if (selectionState.error) {
console.warn('Failed to load RTK selection state:', selectionState.error);
}

const presets = presetsState.value ?? [];
const hasSelectionFromServer = selectionState.value !== undefined;
const selectedOnServer =
selectionState.value !== undefined
Expand All @@ -44,16 +55,27 @@ const RTKCorrectionSourceSelector = ({ onSourceChanged, t }) => {
const hasPresets = presets && presets.length > 0;

const handleChange = async (event) => {
const newPresetId = event.target.value;

// We assume that the request will succeed so we eagerly select the new
// value. If changing the RTK source fails, it will be changed back in the
// response handler triggered by the effect that we set up below.
setSelectedByUser(event.target.value);
setSelectedByUser(newPresetId);

if (onSourceChanged) {
onSourceChanged();
}
};

// Update current preset ID in Redux
useEffect(() => {
const currentId = selectedByUser ?? selectedOnServer;
// Convert NULL_ID to undefined or keep as is? The selector uses 'undefined' for no selection usually.
// But presets have IDs.
const effectiveId = currentId === NULL_ID ? undefined : currentId;
setCurrentRTKPresetId(effectiveId);
}, [selectedByUser, selectedOnServer, setCurrentRTKPresetId]);

// If we have the preset list, but we don't have the current selection yet,
// load the current selection
useEffect(() => {
Expand Down Expand Up @@ -143,14 +165,15 @@ const RTKCorrectionSourceSelector = ({ onSourceChanged, t }) => {

RTKCorrectionSourceSelector.propTypes = {
onSourceChanged: PropTypes.func,
setCurrentRTKPresetId: PropTypes.func,
t: PropTypes.func,
};

export default connect(
// mapStateToProps
null,
// mapDispatchToProps
{
onSourceChanged: resetRTKStatistics,
setCurrentRTKPresetId,
}
)(withTranslation()(RTKCorrectionSourceSelector));
38 changes: 21 additions & 17 deletions src/features/rtk/RTKSetupDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import RTKCorrectionSourceSelector from './RTKCorrectionSourceSelector';
import RTKCoordinateRestorationDialog from './RTKCoordinateRestorationDialog';
import RTKMessageStatistics from './RTKMessageStatistics';
import RTKSetupDialogBottomPanel from './RTKSetupDialogBottomPanel';
import RTKStatusUpdater from './RTKStatusUpdater';
Expand All @@ -14,25 +15,28 @@ import { closeRTKSetupDialog } from './slice';
* monitor the RTK correction source for the UAVs.
*/
const RTKSetupDialog = ({ onClose, open }) => (
<Dialog fullWidth open={open} maxWidth='sm' onClose={onClose}>
<RTKStatusUpdater />
<Box>
<Box sx={{ mx: 3, mt: 3 }}>
<RTKCorrectionSourceSelector />
<Box
sx={{
height: 100,
my: 2,
boxSizing: 'content-box',
overflow: 'auto',
}}
>
<RTKMessageStatistics />
<>
<Dialog fullWidth open={open} maxWidth='sm' onClose={onClose}>
<RTKStatusUpdater />
<Box>
<Box sx={{ mx: 3, mt: 3 }}>
<RTKCorrectionSourceSelector />
<Box
sx={{
height: 100,
my: 2,
boxSizing: 'content-box',
overflow: 'auto',
}}
>
<RTKMessageStatistics />
</Box>
</Box>
<RTKSetupDialogBottomPanel />
</Box>
<RTKSetupDialogBottomPanel />
</Box>
</Dialog>
</Dialog>
<RTKCoordinateRestorationDialog />
</>
);

RTKSetupDialog.propTypes = {
Expand Down
Loading