Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Change take off point and task description page mobile responsive #234

Merged
merged 22 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a2d6bbc
feat(task-description): update popup data
Sep 18, 2024
c38c2b1
feat(task-description): update download flight plan design
Sep 18, 2024
43ebca0
feat: add useWindowDimensions hook
Sep 23, 2024
c580142
feat(task-description): make mobile responsive
Sep 23, 2024
dc8a6c9
feat(task-description): create popup to choose take off point updatio…
Sep 23, 2024
b0cdcbd
feat(task-description): add radio button to as take off point updatio…
Sep 23, 2024
9ca0c32
feat: add turf/helpers package
Sep 23, 2024
bdd290f
feat: add component to get coordinates on mouse click
Sep 23, 2024
2b04260
feat: add ShowInfo component to show information on map
Sep 23, 2024
a6da50f
feat(task-description): get coodrinates of current user location or …
Sep 23, 2024
781b5fc
feat(task-description): plot new takeoff point geojson on map
Sep 23, 2024
3eff55f
Merge branch 'develop' of github.com:hotosm/drone-tm into feat/change…
Sep 23, 2024
043cecc
style(task-description): update info message style
Sep 23, 2024
7a40d8e
feat(task-description): add waypoint endpoint
Sep 23, 2024
b32698d
Merge branch 'develop' of github.com:hotosm/drone-tm into feat/change…
Sep 23, 2024
b481439
feat(task-description): find bbox with updated take-off point and fit…
Sep 24, 2024
c5318bd
feat(task-description): update flight take off point point
Sep 24, 2024
90f55e8
feat(task-description): mobile responsive file upload section
Sep 24, 2024
2e6b2dc
feat(task-description): close modal only instend of redirecting to da…
Sep 24, 2024
1b7520a
fix: merge conflict
Sep 24, 2024
3358ec2
fix: presigned url expiry time
nrjadkry Sep 24, 2024
76a4d31
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 24, 2024
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
4 changes: 2 additions & 2 deletions src/backend/app/projects/project_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
11 changes: 7 additions & 4 deletions src/backend/app/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from minio import Minio
from io import BytesIO
from typing import Any
from datetime import timedelta


def s3_client():
Expand Down Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const ImageCard = ({
return (
<>
<div
className="naxatw-flex naxatw-h-24 naxatw-w-[6.75rem] naxatw-flex-col naxatw-gap-1 naxatw-rounded-lg naxatw-bg-gray-100 naxatw-px-1 hover:naxatw-bg-gray-300"
className="naxatw-flex naxatw-h-24 naxatw-w-full naxatw-flex-col naxatw-gap-1 naxatw-rounded-lg naxatw-bg-gray-100 naxatw-px-1 hover:naxatw-bg-gray-300"
role="presentation"
onClick={() => dispatch(setSelectedImage(image))}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -151,7 +148,7 @@ const ImageBoxPopOver = () => {
className={`naxatw-grid naxatw-gap-4 ${clickedImage ? 'naxatw-grid-cols-[70%_auto]' : 'naxatw-grid-cols-1'}`}
>
<div
className={`scrollbar-images-grid naxatw-grid naxatw-h-[28rem] naxatw-gap-4 naxatw-overflow-y-auto ${clickedImage ? 'naxatw-grid-cols-5' : 'naxatw-grid-cols-6'}`}
className={`scrollbar-images-grid naxatw-grid naxatw-h-[28rem] naxatw-gap-4 naxatw-overflow-y-auto ${clickedImage ? 'naxatw-grid-cols-2 md:naxatw-grid-cols-5' : 'naxatw-grid-cols-3 md:naxatw-grid-cols-6'}`}
>
{imageObject?.map((image, index) => (
<ImageCard
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Icon from '@Components/common/Icon';
import { useNavigate } from 'react-router-dom';
import { toggleModal } from '@Store/actions/common';
import { useDispatch } from 'react-redux';

interface IFilesUploadingPopOverProps {
show: boolean;
Expand All @@ -14,12 +15,14 @@ const FilesUploadingPopOver = ({
filesLength,
uploadedFiles,
}: IFilesUploadingPopOverProps) => {
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;
}
Expand Down Expand Up @@ -49,8 +52,8 @@ const FilesUploadingPopOver = ({
<p className="naxatw-text-[0.875rem] naxatw-text-[#7A7676]">
{uploadedFiles === filesLength && uploadedFiles !== 0 ? (
<>
<p>Redirecting to Dashboard ...</p>
{redirectToDashboard()}
{/* <p>Redirecting to Dashboard ...</p> */}
{closeModal()}
</>
) : (
`${uploadedFiles} / ${filesLength} Files Uploaded`
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<boolean>(false);
const { width } = useWindowDimensions();

const { data: taskDescription }: Record<string, any> =
useGetIndividualTaskQuery(taskId as string);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -147,38 +136,48 @@ const DroneOperatorDescriptionBox = () => {
<p className="naxatw-text-[0.875rem] naxatw-font-normal naxatw-leading-normal naxatw-text-[#484848]">
Task #{taskDescription?.project_task_index}
</p>
<div className="naxatw-flex naxatw-gap-1">
<Button
variant="ghost"
className="naxatw-border naxatw-border-[#D73F3F] naxatw-text-[0.875rem] naxatw-text-[#D73F3F]"
leftIcon="download"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => downloadGeojson()}
>
Download Waypoints Geojson
</Button>

<div className="naxatw-relative">
<Button
variant="ghost"
className="naxatw-border naxatw-border-[#D73F3F] naxatw-text-[0.875rem] naxatw-text-[#D73F3F]"
leftIcon="download"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => handleDownloadFlightPlan()}
onClick={() => setShowDownloadOptions(prev => !prev)}
>
Download Flight Plan
Download
</Button>
{showDownloadOptions && (
<div className="naxatw-absolute naxatw-right-0 naxatw-top-10 naxatw-z-20 naxatw-w-[140px] naxatw-rounded-sm naxatw-border naxatw-bg-white naxatw-shadow-2xl">
<div
className="naxatw-cursor-pointer naxatw-px-3 naxatw-py-2 hover:naxatw-bg-redlight"
role="button"
tabIndex={0}
onKeyDown={() => handleDownloadFlightPlan()}
onClick={() => {
handleDownloadFlightPlan();
setShowDownloadOptions(false);
}}
>
Download flight plan
</div>
<div
className="naxatw-cursor-pointer naxatw-px-3 naxatw-py-2 hover:naxatw-bg-redlight"
role="button"
tabIndex={0}
onKeyDown={() => downloadGeojson()}
onClick={() => {
downloadGeojson();
setShowDownloadOptions(false);
}}
>
Download geojson
</div>
</div>
)}
</div>
</div>
<Tab
onTabChange={value => {
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 && <MapSection />}
{renderComponent(secondPageState)}
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
interface IShowInfo {
heading?: string;
message: string;
wrapperClassName?: string;
className?: string;
}

const ShowInfo = ({
message,
className,
heading,
wrapperClassName,
}: IShowInfo) => {
return (
<div
className={`naxatw-absolute naxatw-left-[calc(50%-7.5rem)] naxatw-top-2 naxatw-z-10 naxatw-w-[15rem] naxatw-rounded-lg naxatw-bg-white naxatw-p-2 naxatw-shadow-xl ${wrapperClassName}`}
>
<div className="naxatw-flex naxatw-items-center naxatw-gap-1">
<i className="material-icons-outlined naxatw-text-base">info</i>{' '}
<h6 className="naxatw-text-base naxatw-font-semibold naxatw-text-gray-600">
{heading}
</h6>
</div>
<div className={`naxatw-text-xs naxatw-text-gray-500 ${className}`}>
{message}
</div>
</div>
);
};

export default ShowInfo;
Loading