diff --git a/src/backend/app/projects/project_logic.py b/src/backend/app/projects/project_logic.py
index 2a5e6492..01a36390 100644
--- a/src/backend/app/projects/project_logic.py
+++ b/src/backend/app/projects/project_logic.py
@@ -200,12 +200,12 @@ async def get_project_info_from_s3(project_id: uuid.UUID, task_id: uuid.UUID):
# Generate a presigned URL for the assets ZIP file
try:
# Check if the object exists
- assets_path = f"processed/{project_id}/{task_id}/assets.zip"
+ assets_path = f"projects/{project_id}/{task_id}/assets.zip"
get_object_metadata(settings.S3_BUCKET_NAME, assets_path)
# If it exists, generate the presigned URL
presigned_url = get_presigned_url(
- settings.S3_BUCKET_NAME, assets_path, expires=3600
+ settings.S3_BUCKET_NAME, assets_path, expires=2
)
except S3Error as e:
if e.code == "NoSuchKey":
diff --git a/src/backend/app/s3.py b/src/backend/app/s3.py
index 68aaf6d7..7120ccba 100644
--- a/src/backend/app/s3.py
+++ b/src/backend/app/s3.py
@@ -3,6 +3,7 @@
from minio import Minio
from io import BytesIO
from typing import Any
+from datetime import timedelta
def s3_client():
@@ -182,20 +183,22 @@ def list_objects_from_bucket(bucket_name: str, prefix: str):
return objects
-def get_presigned_url(bucket_name: str, object_name: str, expires: int = 3600):
+def get_presigned_url(bucket_name: str, object_name: str, expires: int = 2):
"""Generate a presigned URL for an object in an S3 bucket.
Args:
bucket_name (str): The name of the S3 bucket.
object_name (str): The name of the object in the bucket.
- expires (int, optional): The time in seconds until the URL expires.
- Defaults to 3600.
+ expires (int, optional): The time in hours until the URL expires.
+ Defaults to 2 hour.
Returns:
str: The presigned URL to access the object.
"""
client = s3_client()
- return client.presigned_get_object(bucket_name, object_name, expires=expires)
+ return client.presigned_get_object(
+ bucket_name, object_name, expires=timedelta(hours=expires)
+ )
def get_object_metadata(bucket_name: str, object_name: str):
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 613f7b14..f07f9e6a 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -15,6 +15,7 @@
"@turf/area": "^7.0.0",
"@turf/bbox": "^7.0.0",
"@turf/centroid": "^7.0.0",
+ "@turf/helpers": "^7.0.0",
"@turf/meta": "^7.0.0",
"@turf/flatten": "^7.0.0",
"@turf/length": "^7.0.0",
diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/ImageCard/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/ImageCard/index.tsx
index c9bb0557..fb23e168 100644
--- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/ImageCard/index.tsx
+++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/ImageCard/index.tsx
@@ -21,7 +21,7 @@ const ImageCard = ({
return (
<>
dispatch(setSelectedImage(image))}
>
diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx
index 9c5bf842..6b0bf2b7 100644
--- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx
+++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox/index.tsx
@@ -3,27 +3,24 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-console */
/* eslint-disable no-unused-vars */
-import { useEffect, useState, useRef } from 'react';
-import { motion } from 'framer-motion';
-import { useParams } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
+import { motion } from 'framer-motion';
+import { useEffect, useRef, useState } from 'react';
-import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
+import { Button } from '@Components/RadixComponents/Button';
+import { getImageUploadLink } from '@Services/droneOperator';
import {
+ checkAllImages,
setCheckedImages,
- showPopover,
unCheckAllImages,
- checkAllImages,
} from '@Store/actions/droneOperatorTask';
-import Icon from '@Components/common/Icon';
-import { Button } from '@Components/RadixComponents/Button';
-import { getImageUploadLink } from '@Services/droneOperator';
-import delay from '@Utils/createDelay';
-import chunkArray from '@Utils/createChunksOfArray';
+import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import callApiSimultaneously from '@Utils/callApiSimultaneously';
+import chunkArray from '@Utils/createChunksOfArray';
+import delay from '@Utils/createDelay';
import widthCalulator from '@Utils/percentageCalculator';
-import ImageCard from './ImageCard';
import FilesUploadingPopOver from '../LoadingBox';
+import ImageCard from './ImageCard';
import PreviewImage from './PreviewImage';
// interface IImageBoxPopOverProps {
@@ -151,7 +148,7 @@ const ImageBoxPopOver = () => {
className={`naxatw-grid naxatw-gap-4 ${clickedImage ? 'naxatw-grid-cols-[70%_auto]' : 'naxatw-grid-cols-1'}`}
>
{imageObject?.map((image, index) => (
{
- const navigate = useNavigate();
+ const dispatch = useDispatch();
+ // const navigate = useNavigate();
- // function to redirect to dashboard after 2 seconds
- function redirectToDashboard() {
+ // function to close modal
+ function closeModal() {
setTimeout(() => {
- navigate('/dashboard');
+ // navigate('/dashboard');
+ dispatch(toggleModal());
}, 2000);
return null;
}
@@ -49,8 +52,8 @@ const FilesUploadingPopOver = ({
{uploadedFiles === filesLength && uploadedFiles !== 0 ? (
<>
-
Redirecting to Dashboard ...
- {redirectToDashboard()}
+ {/* Redirecting to Dashboard ...
*/}
+ {closeModal()}
>
) : (
`${uploadedFiles} / ${filesLength} Files Uploaded`
diff --git a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx
index 9e205376..f49d37d3 100644
--- a/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx
+++ b/src/frontend/src/components/DroneOperatorTask/DescriptionSection/index.tsx
@@ -1,16 +1,16 @@
/* eslint-disable no-nested-ternary */
-import { useState, useEffect } from 'react';
-import { useParams } from 'react-router-dom';
-import { motion } from 'framer-motion';
-import { Button } from '@Components/RadixComponents/Button';
-import Tab from '@Components/common/Tabs';
import { useGetIndividualTaskQuery, useGetTaskWaypointQuery } from '@Api/tasks';
-import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
-import { setSecondPageState } from '@Store/actions/droneOperatorTask';
+import { Button } from '@Components/RadixComponents/Button';
+import useWindowDimensions from '@Hooks/useWindowDimensions';
+import { useTypedSelector } from '@Store/hooks';
import hasErrorBoundary from '@Utils/hasErrorBoundary';
+import { motion } from 'framer-motion';
+import { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
import { toast } from 'react-toastify';
-import UploadsBox from './UploadsBox';
+import MapSection from '../MapSection';
import DescriptionBox from './DescriptionBox';
+import UploadsBox from './UploadsBox';
const { BASE_URL } = process.env;
@@ -19,6 +19,9 @@ const DroneOperatorDescriptionBox = () => {
const secondPageStates = useTypedSelector(state => state.droneOperatorTask);
const { secondPageState, secondPage } = secondPageStates;
const [animated, setAnimated] = useState(false);
+ const [showDownloadOptions, setShowDownloadOptions] =
+ useState(false);
+ const { width } = useWindowDimensions();
const { data: taskDescription }: Record =
useGetIndividualTaskQuery(taskId as string);
@@ -78,20 +81,6 @@ const DroneOperatorDescriptionBox = () => {
);
}
};
- const dispatch = useTypedDispatch();
-
- const headerTabOptions = [
- {
- id: 1,
- label: 'Description',
- value: 'description',
- },
- {
- id: 2,
- label: 'Uploads',
- value: 'uploads',
- },
- ];
const handleDownloadFlightPlan = () => {
fetch(
@@ -147,38 +136,48 @@ const DroneOperatorDescriptionBox = () => {
Task #{taskDescription?.project_task_index}
-
-
+
+
+ {showDownloadOptions && (
+
+
handleDownloadFlightPlan()}
+ onClick={() => {
+ handleDownloadFlightPlan();
+ setShowDownloadOptions(false);
+ }}
+ >
+ Download flight plan
+
+
downloadGeojson()}
+ onClick={() => {
+ downloadGeojson();
+ setShowDownloadOptions(false);
+ }}
+ >
+ Download geojson
+
+
+ )}
- {
- dispatch(setSecondPageState(value));
- }}
- tabOptions={headerTabOptions}
- activeTab={secondPageState}
- orientation="row"
- className={`naxatw-h-[3rem] naxatw-border-b naxatw-bg-transparent hover:naxatw-border-b-2 hover:naxatw-border-red ${!secondPage ? 'naxatw-hidden' : 'naxatw-block'}`}
- activeClassName="naxatw-border-b-2 naxatw-bg-transparent naxatw-border-red"
- clickable
- />
+ {width < 640 && }
{renderComponent(secondPageState)}
>
diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx
new file mode 100644
index 00000000..66659ad5
--- /dev/null
+++ b/src/frontend/src/components/DroneOperatorTask/MapSection/GetCoordinatesOnClick.tsx
@@ -0,0 +1,31 @@
+/* eslint-disable no-param-reassign */
+import { MapInstanceType } from '@Components/common/MapLibreComponents/types';
+import { useEffect } from 'react';
+
+interface IGetCoordinatesOnClick {
+ map?: MapInstanceType;
+ isMapLoaded?: Boolean;
+ getCoordinates: any;
+}
+
+const GetCoordinatesOnClick = ({
+ map,
+ isMapLoaded,
+ getCoordinates,
+}: IGetCoordinatesOnClick) => {
+ useEffect(() => {
+ if (!map || !isMapLoaded) return () => {};
+ map.getCanvas().style.cursor = 'crosshair';
+ map.on('click', e => {
+ const latLng = e.lngLat;
+ getCoordinates(latLng);
+ });
+
+ return () => {
+ map.getCanvas().style.cursor = '';
+ };
+ }, [map, isMapLoaded, getCoordinates]);
+ return null;
+};
+
+export default GetCoordinatesOnClick;
diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/ShowInfo.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/ShowInfo.tsx
new file mode 100644
index 00000000..e16aa1a5
--- /dev/null
+++ b/src/frontend/src/components/DroneOperatorTask/MapSection/ShowInfo.tsx
@@ -0,0 +1,31 @@
+interface IShowInfo {
+ heading?: string;
+ message: string;
+ wrapperClassName?: string;
+ className?: string;
+}
+
+const ShowInfo = ({
+ message,
+ className,
+ heading,
+ wrapperClassName,
+}: IShowInfo) => {
+ return (
+
+
+ info{' '}
+
+ {heading}
+
+
+
+ {message}
+
+
+ );
+};
+
+export default ShowInfo;
diff --git a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx
index 5ddd3954..5666af81 100644
--- a/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx
+++ b/src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx
@@ -1,24 +1,37 @@
/* eslint-disable react/no-array-index-key */
-import { useCallback, useEffect, useState } from 'react';
-import { LngLatBoundsLike, Map } from 'maplibre-gl';
-import { useParams } from 'react-router-dom';
-import { FeatureCollection } from 'geojson';
import { useGetTaskWaypointQuery } from '@Api/tasks';
-import getBbox from '@turf/bbox';
-import { coordAll } from '@turf/meta';
+import marker from '@Assets/images/marker.png';
+import right from '@Assets/images/rightArrow.png';
+import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher';
import { useMapLibreGLMap } from '@Components/common/MapLibreComponents';
+import AsyncPopup from '@Components/common/MapLibreComponents/AsyncPopup';
import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer';
+import LocateUser from '@Components/common/MapLibreComponents/LocateUser';
import MapContainer from '@Components/common/MapLibreComponents/MapContainer';
import { GeojsonType } from '@Components/common/MapLibreComponents/types';
-import right from '@Assets/images/rightArrow.png';
-import marker from '@Assets/images/marker.png';
+import { Button } from '@Components/RadixComponents/Button';
+import { postTaskWaypoint } from '@Services/tasks';
+import { toggleModal } from '@Store/actions/common';
+import { setSelectedTakeOffPoint } from '@Store/actions/droneOperatorTask';
+import { useTypedSelector } from '@Store/hooks';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import getBbox from '@turf/bbox';
+import { point } from '@turf/helpers';
+import { coordAll } from '@turf/meta';
import hasErrorBoundary from '@Utils/hasErrorBoundary';
-import AsyncPopup from '@Components/common/MapLibreComponents/AsyncPopup';
-import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher';
-import LocateUser from '@Components/common/MapLibreComponents/LocateUser';
+import { FeatureCollection } from 'geojson';
+import { LngLatBoundsLike, Map } from 'maplibre-gl';
+import { useCallback, useEffect, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import { toast } from 'react-toastify';
+import GetCoordinatesOnClick from './GetCoordinatesOnClick';
+import ShowInfo from './ShowInfo';
-const MapSection = () => {
+const MapSection = ({ className }: { className?: string }) => {
+ const dispatch = useDispatch();
const { projectId, taskId } = useParams();
+ const queryClient = useQueryClient();
const [popupData, setPopupData] = useState
>({});
const { map, isMapLoaded } = useMapLibreGLMap({
containerId: 'dashboard-map',
@@ -29,6 +42,9 @@ const MapSection = () => {
},
disableRotation: true,
});
+ const newTakeOffPoint = useTypedSelector(
+ state => state.droneOperatorTask.selectedTakeOffPoint,
+ );
const { data: taskWayPoints }: any = useGetTaskWaypointQuery(
projectId as string,
@@ -56,13 +72,42 @@ const MapSection = () => {
},
);
- // zoom to task
+ const { mutate: postWaypoint, isLoading: isUpdatingTakeOffPoint } =
+ useMutation({
+ mutationFn: postTaskWaypoint,
+ onSuccess: async data => {
+ // update task cached waypoint data with response
+ queryClient.setQueryData(['task-waypoints'], () => {
+ return data;
+ });
+ dispatch(setSelectedTakeOffPoint(null));
+ },
+ onError: (err: any) => {
+ toast.error(err?.response?.data?.detail || err.message);
+ },
+ });
+
+ // zoom to task (waypoint)
useEffect(() => {
- if (!taskWayPoints?.geojsonAsLineString) return;
+ if (!taskWayPoints?.geojsonAsLineString || !isMapLoaded || !map) return;
const { geojsonAsLineString } = taskWayPoints;
- const bbox = getBbox(geojsonAsLineString as FeatureCollection);
+ let bbox = null;
+ // calculate bbox with with updated take-off point
+ if (newTakeOffPoint && newTakeOffPoint !== 'place_on_map') {
+ const combinedFeatures: FeatureCollection = {
+ type: 'FeatureCollection',
+ features: [
+ ...geojsonAsLineString.features,
+ // @ts-ignore
+ newTakeOffPoint,
+ ],
+ };
+ bbox = getBbox(combinedFeatures);
+ } else {
+ bbox = getBbox(geojsonAsLineString as FeatureCollection);
+ }
map?.fitBounds(bbox as LngLatBoundsLike, { padding: 25, duration: 500 });
- }, [map, taskWayPoints]);
+ }, [map, taskWayPoints, newTakeOffPoint, isMapLoaded]);
const getPopupUI = useCallback(() => {
return (
@@ -70,15 +115,20 @@ const MapSection = () => {
{popupData?.index}
- {popupData?.coordinates?.lat?.toFixed(8)}, {popupData?.coordinates?.lng?.toFixed(8)}{' '}
+ {popupData?.coordinates?.lat?.toFixed(8)},
+ {popupData?.coordinates?.lng?.toFixed(8)}{' '}
Speed: {popupData?.speed} m/s
- {popupData?.elevation &&
-
Elevation (Sea Level): {popupData?.elevation} meter
- }
-
Take Photo: {popupData?.take_photo ? "True" : "False"}
+ {popupData?.elevation && (
+
+ Elevation (Sea Level): {popupData?.elevation} meter{' '}
+
+ )}
+
+ Take Photo: {popupData?.take_photo ? 'True' : 'False'}
+
Gimble angle: {popupData?.gimbal_angle} degree
@@ -92,9 +142,31 @@ const MapSection = () => {
);
}, [popupData]);
+ const handleSaveStartingPoint = () => {
+ const { geometry } = newTakeOffPoint as Record
;
+ const [lng, lat] = geometry.coordinates;
+ postWaypoint({
+ projectId,
+ taskId,
+ data: {
+ longitude: lng,
+ latitude: lat,
+ },
+ });
+ };
+
+ useEffect(
+ () => () => {
+ dispatch(setSelectedTakeOffPoint(null));
+ },
+ [dispatch],
+ );
+
return (
<>
-
+
{
>
)}
+
+
+
+
+ {newTakeOffPoint && (
+
+ )}
+
+ {newTakeOffPoint === 'place_on_map' && (
+ ) =>
+ dispatch(
+ setSelectedTakeOffPoint(
+ point([coordinates.lng, coordinates?.lat]),
+ ),
+ )
+ }
+ />
+ )}
+
+ {newTakeOffPoint === 'place_on_map' && (
+
+ )}
+
) =>
diff --git a/src/frontend/src/components/DroneOperatorTask/ModalContent/ChooseTakeOffPointOptions.tsx b/src/frontend/src/components/DroneOperatorTask/ModalContent/ChooseTakeOffPointOptions.tsx
new file mode 100644
index 00000000..1cb16b92
--- /dev/null
+++ b/src/frontend/src/components/DroneOperatorTask/ModalContent/ChooseTakeOffPointOptions.tsx
@@ -0,0 +1,61 @@
+import RadioButton from '@Components/common/RadioButton';
+import { Button } from '@Components/RadixComponents/Button';
+import { takeOffPointOptions } from '@Constants/taskDescription';
+import { toggleModal } from '@Store/actions/common';
+import {
+ setSelectedTakeOffPoint,
+ setSelectedTakeOffPointOption,
+} from '@Store/actions/droneOperatorTask';
+import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
+import { point } from '@turf/helpers';
+
+const ChooseTakeOffPointOptions = () => {
+ const dispatch = useTypedDispatch();
+ const selectedTakeOffPointOption = useTypedSelector(
+ state => state.droneOperatorTask.selectedTakeOffPointOption,
+ );
+
+ const handleNextClick = () => {
+ if (selectedTakeOffPointOption === 'current_location') {
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(latLng =>
+ dispatch(
+ setSelectedTakeOffPoint(
+ point([latLng?.coords?.longitude, latLng?.coords?.latitude]),
+ ),
+ ),
+ );
+ }
+ } else {
+ dispatch(setSelectedTakeOffPoint(selectedTakeOffPointOption));
+ }
+ dispatch(toggleModal());
+ };
+ return (
+
+
+ Please select the take-off point for your drone.
+
+
+ dispatch(setSelectedTakeOffPointOption(value))}
+ value={selectedTakeOffPointOption}
+ />
+
+
+
+
+
+ );
+};
+
+export default ChooseTakeOffPointOptions;
diff --git a/src/frontend/src/constants/modalContents.tsx b/src/frontend/src/constants/modalContents.tsx
index a5eb0a79..caecbacd 100644
--- a/src/frontend/src/constants/modalContents.tsx
+++ b/src/frontend/src/constants/modalContents.tsx
@@ -1,11 +1,13 @@
-import { ReactElement } from 'react';
import ExitCreateProjectModal from '@Components/CreateProject/ExitCreateProjectModal';
import ImageBoxPopOver from '@Components/DroneOperatorTask/DescriptionSection/PopoverBox/ImageBox';
+import ChooseTakeOffPointOptions from '@Components/DroneOperatorTask/ModalContent/ChooseTakeOffPointOptions';
+import { ReactElement } from 'react';
export type ModalContentsType =
| 'sign-up-success'
| 'quit-create-project'
| 'raw-image-preview'
+ | 'update-flight-take-off-point'
| null;
export type PromptDialogContentsType = 'delete-layer' | null;
@@ -31,10 +33,16 @@ export function getModalContent(content: ModalContentsType): ModalReturnType {
case 'raw-image-preview':
return {
- className: '!naxatw-w-[60vw]',
+ className: '!naxatw-w-[95vw] md:!naxatw-w-[60vw]',
title: 'Upload Raw Image',
content: ,
};
+ case 'update-flight-take-off-point':
+ return {
+ className: 'naxatw-w-[92vw] naxatw-max-w-[25rem]',
+ title: 'Take-off Point',
+ content: ,
+ };
default:
return {
diff --git a/src/frontend/src/constants/taskDescription.ts b/src/frontend/src/constants/taskDescription.ts
new file mode 100644
index 00000000..afeebfb4
--- /dev/null
+++ b/src/frontend/src/constants/taskDescription.ts
@@ -0,0 +1,14 @@
+/* eslint-disable import/prefer-default-export */
+
+export const takeOffPointOptions = [
+ {
+ label: 'My current location',
+ value: 'current_location',
+ name: 'take_off_point',
+ },
+ {
+ label: 'Place on map',
+ value: 'place_on_map',
+ name: 'take_off_point',
+ },
+];
diff --git a/src/frontend/src/hooks/useWindowDimensions.tsx b/src/frontend/src/hooks/useWindowDimensions.tsx
new file mode 100644
index 00000000..ee0a0de8
--- /dev/null
+++ b/src/frontend/src/hooks/useWindowDimensions.tsx
@@ -0,0 +1,36 @@
+import { useState, useEffect } from 'react';
+
+function debounce(func: Function, timeout = 300) {
+ let timer: any;
+ return (arg: any) => {
+ clearTimeout(timer);
+ timer = setTimeout(() => {
+ func(arg);
+ }, timeout);
+ };
+}
+
+const useWindowDimensions = () => {
+ const [dimensions, setDimensions] = useState({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+
+ useEffect(() => {
+ const handleResize = debounce(() => {
+ setDimensions({
+ width: window.innerWidth,
+ height: window.innerHeight,
+ });
+ }, 100);
+ window.addEventListener('resize', handleResize);
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, []);
+
+ return dimensions;
+};
+
+export default useWindowDimensions;
diff --git a/src/frontend/src/services/tasks.ts b/src/frontend/src/services/tasks.ts
index 67561a69..47a7f4d1 100644
--- a/src/frontend/src/services/tasks.ts
+++ b/src/frontend/src/services/tasks.ts
@@ -8,5 +8,15 @@ export const getTaskWaypoint = (projectId: string, taskId: string) =>
export const getIndividualTask = (taskId: string) =>
authenticated(api).get(`/tasks/${taskId}`);
+export const postTaskWaypoint = (payload: Record) => {
+ const { taskId, projectId, data } = payload;
+ return authenticated(api).post(
+ `/waypoint/task/${taskId}/?project_id=${projectId}&download=false`,
+ data,
+ {
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+};
export const getTaskAssetsInfo = (projectId: string, taskId: string) =>
authenticated(api).get(`/projects/assets/${projectId}/${taskId}/`);
diff --git a/src/frontend/src/store/actions/droneOperatorTask.ts b/src/frontend/src/store/actions/droneOperatorTask.ts
index 5564edd5..19e24a36 100644
--- a/src/frontend/src/store/actions/droneOperatorTask.ts
+++ b/src/frontend/src/store/actions/droneOperatorTask.ts
@@ -10,4 +10,6 @@ export const {
unCheckAllImages,
checkAllImages,
setFiles,
+ setSelectedTakeOffPointOption,
+ setSelectedTakeOffPoint,
} = droneOperatorTaskSlice.actions;
diff --git a/src/frontend/src/store/slices/droneOperartorTask.ts b/src/frontend/src/store/slices/droneOperartorTask.ts
index 5bb88bbc..85f33d4a 100644
--- a/src/frontend/src/store/slices/droneOperartorTask.ts
+++ b/src/frontend/src/store/slices/droneOperartorTask.ts
@@ -8,6 +8,8 @@ export interface IDroneOperatorTaskState {
checkedImages: Record;
popOver: boolean;
files: any[];
+ selectedTakeOffPointOption: string;
+ selectedTakeOffPoint: any[] | string | null;
}
const initialState: IDroneOperatorTaskState = {
@@ -17,6 +19,8 @@ const initialState: IDroneOperatorTaskState = {
checkedImages: {},
popOver: false,
files: [],
+ selectedTakeOffPointOption: 'current_location',
+ selectedTakeOffPoint: null,
};
export const droneOperatorTaskSlice = createSlice({
@@ -55,6 +59,12 @@ export const droneOperatorTaskSlice = createSlice({
setFiles: (state, action) => {
state.files = action.payload;
},
+ setSelectedTakeOffPointOption: (state, action) => {
+ state.selectedTakeOffPointOption = action.payload;
+ },
+ setSelectedTakeOffPoint: (state, action) => {
+ state.selectedTakeOffPoint = action.payload;
+ },
},
});
diff --git a/src/frontend/src/views/TaskDescription/index.tsx b/src/frontend/src/views/TaskDescription/index.tsx
index daa5753e..fa7b0088 100644
--- a/src/frontend/src/views/TaskDescription/index.tsx
+++ b/src/frontend/src/views/TaskDescription/index.tsx
@@ -1,18 +1,21 @@
import DroneOperatorDescriptionBox from '@Components/DroneOperatorTask/DescriptionSection';
import DroneOperatorTaskHeader from '@Components/DroneOperatorTask/Header';
import MapSection from '@Components/DroneOperatorTask/MapSection';
+import useWindowDimensions from '@Hooks/useWindowDimensions';
import hasErrorBoundary from '@Utils/hasErrorBoundary';
const TaskDescription = () => {
+ const { width } = useWindowDimensions();
+
return (
<>