Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 9 additions & 11 deletions src/components/MiniTable.jsx → src/components/MiniTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isNil from 'lodash-es/isNil';
import PropTypes from 'prop-types';
import type React from 'react';

import { makeStyles } from '@skybrush/app-theme-mui';

Expand All @@ -22,13 +22,17 @@ const useStyles = makeStyles((theme) => ({
value: {
textAlign: 'right',
},

separator: {},
}));

export const naText = <span className='muted'>—</span>;

const MiniTable = ({ items }) => {
export type MiniTableItem = string | [string, React.ReactNode];
Comment thread
ntamas marked this conversation as resolved.

type MiniTableProps = {
items: MiniTableItem[];
};

const MiniTable = ({ items }: MiniTableProps) => {
const classes = useStyles();

return (
Expand All @@ -44,7 +48,7 @@ const MiniTable = ({ items }) => {
</tr>
) : (
<tr key={row}>
<td className={classes.separator} colSpan={2} />
<td colSpan={2} />
</tr>
)
)}
Expand All @@ -53,10 +57,4 @@ const MiniTable = ({ items }) => {
);
};

MiniTable.propTypes = {
items: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.node)])
),
};

export default MiniTable;
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import Divider from '@mui/material/Divider';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import PropTypes from 'prop-types';
import { memo, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { useAsyncRetry, useUnmount } from 'react-use';

import type { UAVPreflightCheckInfo } from '@skybrush/flockwave-spec';
import {
BackgroundHint,
FormHeader as Header,
Expand All @@ -24,13 +24,16 @@ import {
describeOverallPreflightCheckResult,
describePreflightCheckResult,
getSemanticsForPreflightCheckResult,
PreflightCheckResult,
} from '~/model/enums';
import CustomPropTypes from '~/utils/prop-types';
import type { RootState } from '~/store/reducers';

import { getUAVById } from './selectors';

const ErrorList = ({ errorCodes }) => {
type ErrorListProps = {
errorCodes?: UAVErrorCode[];
};

const ErrorList = ({ errorCodes }: ErrorListProps) => {
const { t } = useTranslation();
const relevantErrorCodes = (errorCodes || []).filter(
(code) =>
Expand All @@ -56,11 +59,13 @@ const ErrorList = ({ errorCodes }) => {
);
};

ErrorList.propTypes = {
errorCodes: PropTypes.arrayOf(PropTypes.number),
};
type PreflightStatusResultsProps = UAVPreflightCheckInfo;

const PreflightStatusResults = ({ message, result, items }) => {
const PreflightStatusResults = ({
items,
message,
result,
}: PreflightStatusResultsProps) => {
const { t } = useTranslation();
return (
<>
Expand All @@ -86,7 +91,7 @@ const PreflightStatusResults = ({ message, result, items }) => {
<ListItemText
primary={
message ||
(item.result === PreflightCheckResult.PASS
(item.result === 'pass'
? item.label
: `${item.label} — ${describePreflightCheckResult(
item.result,
Expand All @@ -103,103 +108,111 @@ const PreflightStatusResults = ({ message, result, items }) => {
);
};

PreflightStatusResults.propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
label: PropTypes.string,
message: PropTypes.string,
result: CustomPropTypes.preflightCheckResult,
})
),
message: PropTypes.string,
result: CustomPropTypes.preflightCheckResult,
type PreflightStatusPanelLowerSegmentProps = {
uavId?: string;
};

const PreflightStatusPanelLowerSegment = memo(({ uavId }) => {
const messageHub = useMessageHub();
const state = useAsyncRetry(
() => (uavId ? messageHub.query.getPreflightStatus(uavId) : {}),
[messageHub, uavId]
);
const scheduledRefresh = useRef();

// Refresh the status every second
useEffect(() => {
const isResultReady =
uavId && !state.loading && !state.error && Boolean(state.value);
if (isResultReady && !scheduledRefresh.current) {
scheduledRefresh.current = setTimeout(() => {
scheduledRefresh.current = undefined;
state.retry();
}, 1000);
const missingUAVCheckInfo: UAVPreflightCheckInfo = {
items: [],
result: 'off',
};

const PreflightStatusPanelLowerSegment = memo(
({ uavId }: PreflightStatusPanelLowerSegmentProps) => {
const messageHub = useMessageHub();
const state = useAsyncRetry(
() =>
uavId
? messageHub.query.getPreflightStatus(uavId)
: Promise.resolve(missingUAVCheckInfo),
[messageHub, uavId]
);
const scheduledRefresh = useRef<ReturnType<typeof setTimeout>>();

// Refresh the status every second
useEffect(() => {
const isResultReady =
uavId && !state.loading && !state.error && Boolean(state.value);
if (isResultReady && !scheduledRefresh.current) {
scheduledRefresh.current = setTimeout(() => {
scheduledRefresh.current = undefined;
state.retry();
}, 1000);
}
}, [state, uavId]);

// Cancel scheduled refreshes when unmounting
useUnmount(() => {
if (scheduledRefresh.current) {
clearTimeout(scheduledRefresh.current);
}
});

if (state.error && !state.loading) {
return (
<BackgroundHint
icon={<Error />}
text='Error while loading preflight status report'
button={<Button onClick={state.retry}>Try again</Button>}
/>
);
}

if (state.value) {
return (
<PreflightStatusResults
message={state.value.message}
result={state.value.result}
items={state.value.items}
/>
);
}
}, [state, uavId]);

// Cancel scheduled refreshes when unmounting
useUnmount(() => {
if (scheduledRefresh.current) {
clearTimeout(scheduledRefresh.current);
if (state.loading) {
return (
<LargeProgressIndicator
fullHeight
label='Retrieving status report...'
/>
);
}
});

if (state.error && !state.loading) {
return (
<BackgroundHint
icon={<Error />}
text='Error while loading preflight status report'
text='Preflight status report not loaded yet'
button={<Button onClick={state.retry}>Try again</Button>}
/>
);
}
);

if (state.value) {
return (
<PreflightStatusResults
message={state.value.message}
result={state.value.result}
items={state.value.items}
/>
);
}

if (state.loading) {
return (
<LargeProgressIndicator fullHeight label='Retrieving status report...' />
);
}

return (
<BackgroundHint
text='Preflight status report not loaded yet'
button={<Button onClick={state.retry}>Try again</Button>}
/>
);
});
PreflightStatusPanelLowerSegment.displayName =
'PreflightStatusPanelLowerSegment';

PreflightStatusPanelLowerSegment.propTypes = {
uavId: PropTypes.string,
type PreflightStatusPanelOwnProps = {
uavId?: string;
};
type PreflightStatusPanelDiospatchProps = {
errorCodes?: UAVErrorCode[];
};
type PreflightStatusPanelProps = PreflightStatusPanelOwnProps &
PreflightStatusPanelDiospatchProps;

const PreflightStatusPanel = ({ errorCodes, uavId }) => (
const PreflightStatusPanel = ({
errorCodes,
uavId,
}: PreflightStatusPanelProps) => (
<>
<ErrorList errorCodes={errorCodes} />
<PreflightStatusPanelLowerSegment uavId={uavId} />
</>
);

PreflightStatusPanel.propTypes = {
errorCodes: PropTypes.arrayOf(PropTypes.number),
uavId: PropTypes.string,
};

export default connect(
// mapStateToProps
(state, ownProps) => ({
(state: RootState, ownProps: PreflightStatusPanelOwnProps) => ({
errorCodes: ownProps.uavId
? getUAVById(state, ownProps.uavId)?.errors
: null,
}),
// mapDispatchToProps
{}
: undefined,
})
)(PreflightStatusPanel);
5 changes: 1 addition & 4 deletions src/features/uavs/StatusSummaryMiniTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,5 @@ StatusSummaryMiniTable.propTypes = {

export default connect(
// mapStateToProps
(state, ownProps) => getUAVById(state, ownProps.uavId),

// mapDispatchToProps
{}
(state, ownProps) => getUAVById(state, ownProps.uavId) ?? {}
)(StatusSummaryMiniTable);
Original file line number Diff line number Diff line change
@@ -1,44 +1,47 @@
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import MessagesPanel from '~/components/chat/MessagesPanel';
import type { RootState } from '~/store/reducers';

import PreflightStatusPanel from './PreflightStatusPanel';
import UAVLogsPanel from './UAVLogsPanel';
import UAVTestsPanel from './UAVTestsPanel';

import {
getSelectedTabInUAVDetailsDialog,
getSelectedUAVIdInUAVDetailsDialog,
} from './details';
import { UAVDetailsDialogTab } from './types';

type UAVDetailsDialogBodyProps = {
selectedTab: UAVDetailsDialogTab;
uavId?: string;
};

const UAVDetailsDialogBody = ({ selectedTab, uavId }) => {
const UAVDetailsDialogBody = ({
selectedTab,
uavId,
}: UAVDetailsDialogBodyProps) => {
switch (selectedTab) {
case 'messages':
case UAVDetailsDialogTab.MESSAGES:
return <MessagesPanel uavId={uavId} />;

case 'preflight':
case UAVDetailsDialogTab.PREFLIGHT:
return <PreflightStatusPanel uavId={uavId} />;

case 'tests':
case UAVDetailsDialogTab.TESTS:
return <UAVTestsPanel uavId={uavId} />;

case 'logs':
case UAVDetailsDialogTab.LOGS:
return <UAVLogsPanel uavId={uavId} />;

default:
return null;
}
};

UAVDetailsDialogBody.propTypes = {
selectedTab: PropTypes.oneOf(['messages', 'preflight', 'tests', 'logs']),
uavId: PropTypes.string,
};

export default connect(
// mapStateToProps
(state) => ({
(state: RootState) => ({
selectedTab: getSelectedTabInUAVDetailsDialog(state),
uavId: getSelectedUAVIdInUAVDetailsDialog(state),
})
Expand Down
7 changes: 5 additions & 2 deletions src/features/uavs/UAVTestsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type UAVTestButtonProps = {
needsConfirmation?: boolean;
timeout?: number;
type: UAVTestType;
uavId: string;
uavId?: string;
};

const UAVTestButton = ({
Expand Down Expand Up @@ -146,6 +146,9 @@ const UAVTestButton = ({

const [lastExecutionState, execute] = useAsyncFn(async () => {
lastExecutedUavIdRef.current = uavId;
if (uavId === undefined) {
return;
}
// TODO(ntamas): use the proper UAV-TEST messages designated for this
await messageHub.sendCommandRequest(
{
Expand Down Expand Up @@ -242,7 +245,7 @@ const UAVTestButton = ({
};

type UAVTestsPanelProps = {
uavId: string;
uavId?: string;
};

const UAVTestsPanel = ({ uavId }: UAVTestsPanelProps) => {
Expand Down
Loading