From 671dfa176a3e39b3a97f5c2ea5bd3de0c2f2ea3c Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Sat, 21 Dec 2024 12:57:44 +0100 Subject: [PATCH 01/33] chore: linked start mapping button to explore models page --- frontend/src/components/landing/header/header.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/landing/header/header.tsx b/frontend/src/components/landing/header/header.tsx index 47685837..f51617e5 100644 --- a/frontend/src/components/landing/header/header.tsx +++ b/frontend/src/components/landing/header/header.tsx @@ -26,9 +26,15 @@ const Header = () => { {APP_CONTENT.homepage.ctaPrimaryButton} - + + +
From c957ca2d3d506a590b4b9c2260c002b645f76c1c Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Mon, 23 Dec 2024 17:01:26 +0100 Subject: [PATCH 02/33] wip: continuous improvements --- frontend/.env.sample | 14 +- frontend/src/app/router.tsx | 17 +- frontend/src/app/routes/about.tsx | 4 +- frontend/src/app/routes/account/models.tsx | 5 +- frontend/src/app/routes/landing.tsx | 14 +- frontend/src/app/routes/learn.tsx | 7 +- .../src/app/routes/models/confirmation.tsx | 2 +- .../src/app/routes/models/model-details.tsx | 2 +- .../src/app/routes/models/models-list.tsx | 14 +- frontend/src/app/routes/protected-route.tsx | 2 +- frontend/src/assets/images/index.ts | 10 + frontend/src/assets/svgs/index.ts | 7 + frontend/src/components/errors/fallback.tsx | 3 +- frontend/src/components/errors/index.ts | 4 +- .../components/errors/under-construction.tsx | 3 +- .../landing/about-fair/about-fair.tsx | 7 +- .../landing/core-features/core-features.tsx | 4 +- .../landing/core-values/core-values.tsx | 8 +- frontend/src/components/landing/cta/cta.tsx | 7 +- .../src/components/landing/header/header.tsx | 4 +- .../src/components/landing/header/index.ts | 2 +- frontend/src/components/landing/index.ts | 15 +- frontend/src/components/landing/kpi/kpi.tsx | 3 +- .../components/landing/tagline/tagline.tsx | 3 +- .../{ui => layout}/footer/footer.tsx | 5 +- .../src/components/layout/footer/index.ts | 1 + frontend/src/components/layout/index.ts | 2 + .../src/components/layout/navbar/index.ts | 3 + .../{ui => layout}/navbar/nav-logo.tsx | 6 +- .../{ui => layout}/navbar/navbar.module.css | 0 .../src/components/layout/navbar/navbar.tsx | 139 +++++++ .../{ui => layout}/navbar/user-profile.tsx | 6 +- frontend/src/components/layouts/index.ts | 1 - .../components/map/setups/setup-maplibre.ts | 25 +- .../shared/fair-process/fair-process.tsx | 4 +- .../components/shared/faqs/faqs.module.css | 7 - frontend/src/components/shared/index.ts | 6 +- frontend/src/components/shared/pagination.tsx | 4 +- .../{section-header => }/section-header.tsx | 0 .../header.module.css | 3 + .../{header => static-page-header}/header.tsx | 2 +- frontend/src/components/ui/footer/index.ts | 1 - frontend/src/components/ui/icons/index.ts | 1 + .../ui/icons/no-training-area-icon.tsx | 36 ++ frontend/src/components/ui/navbar/index.ts | 3 - frontend/src/components/ui/navbar/navbar.tsx | 12 +- frontend/src/config/env.ts | 17 + frontend/src/enums/index.ts | 1 + frontend/src/enums/training-area.ts | 5 + .../features/model-creation/api/factory.ts | 10 + .../model-creation/api/get-trainings.ts | 13 + .../components/progress-bar.tsx | 21 +- .../training-area/training-area-item.tsx | 389 +++++++++++------- .../training-area/training-area-list.tsx | 43 +- .../model-creation/hooks/use-polling.ts | 69 ++++ .../hooks/use-training-areas.ts | 14 +- .../features/models/api/update-trainings.ts | 2 +- .../dialogs/training-settings-dialog.tsx | 2 +- .../models/components/layout-toggle.tsx | 2 +- .../features/models/components/map-toggle.tsx | 2 +- .../models/components/maps/models-map.tsx | 4 +- .../components/maps/training-area-map.tsx | 10 +- .../features/models/components/model-card.tsx | 2 +- .../models/components/model-details-popup.tsx | 2 +- .../components/training-history-table.tsx | 225 +++++----- .../src/features/models/hooks/use-models.ts | 4 +- .../start-mapping/components/header.tsx | 2 +- frontend/src/hooks/use-map-instance.ts | 11 +- frontend/src/hooks/use-scroll-to-element.ts | 2 +- frontend/src/layouts/index.ts | 2 + .../layouts/model-forms-layout.tsx | 79 ++-- .../{components => }/layouts/root-layout.tsx | 10 +- ...jsonToOsmPolygons.ts => geojson-to-osm.ts} | 0 frontend/src/lib/index.ts | 1 + frontend/src/services/api-routes.ts | 20 +- frontend/src/styles/index.css | 29 +- frontend/src/utils/constants.ts | 14 + frontend/src/utils/general-utils.ts | 2 +- 78 files changed, 963 insertions(+), 474 deletions(-) create mode 100644 frontend/src/assets/images/index.ts create mode 100644 frontend/src/assets/svgs/index.ts rename frontend/src/components/{ui => layout}/footer/footer.tsx (97%) create mode 100644 frontend/src/components/layout/footer/index.ts create mode 100644 frontend/src/components/layout/index.ts create mode 100644 frontend/src/components/layout/navbar/index.ts rename frontend/src/components/{ui => layout}/navbar/nav-logo.tsx (86%) rename frontend/src/components/{ui => layout}/navbar/navbar.module.css (100%) create mode 100644 frontend/src/components/layout/navbar/navbar.tsx rename frontend/src/components/{ui => layout}/navbar/user-profile.tsx (91%) delete mode 100644 frontend/src/components/layouts/index.ts rename frontend/src/components/shared/{section-header => }/section-header.tsx (100%) rename frontend/src/components/shared/{header => static-page-header}/header.module.css (52%) rename frontend/src/components/shared/{header => static-page-header}/header.tsx (85%) delete mode 100644 frontend/src/components/ui/footer/index.ts create mode 100644 frontend/src/components/ui/icons/no-training-area-icon.tsx delete mode 100644 frontend/src/components/ui/navbar/index.ts create mode 100644 frontend/src/enums/training-area.ts create mode 100644 frontend/src/features/model-creation/hooks/use-polling.ts create mode 100644 frontend/src/layouts/index.ts rename frontend/src/{components => }/layouts/model-forms-layout.tsx (78%) rename frontend/src/{components => }/layouts/root-layout.tsx (80%) rename frontend/src/lib/{geojsonToOsmPolygons.ts => geojson-to-osm.ts} (100%) create mode 100644 frontend/src/lib/index.ts diff --git a/frontend/.env.sample b/frontend/.env.sample index 75862b77..2b03dbcb 100644 --- a/frontend/.env.sample +++ b/frontend/.env.sample @@ -114,4 +114,16 @@ VITE_TRAINING_AREAS_AOI_LABELS_OUTLINE_COLOR = "#D73434" # The remote url to JOSM. # Data type: String (e.g., "http://127.0.0.1:8111/"). # Default value: http://127.0.0.1:8111/. -VITE_JOSM_REMOTE_URL = "http://127.0.0.1:8111/" \ No newline at end of file +VITE_JOSM_REMOTE_URL = "http://127.0.0.1:8111/" + + +# The time to poll the backend for the status of the AOI training labels fetching, in milliseconds (ms). +# Data type: Positive Integer (e.g., 900). +# Default value: 5000 milliseconds (5 seconds). +VITE_TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS = 5000 + + +# The time to poll the backend for the status of the OSM last updated time, in milliseconds (ms). +# Data type: Positive Integer (e.g., 900). +# Default value: 10000 milliseconds (10 seconds). +VITE_OSM_LAST_UPDATED_POOLING_INTERVAL_MS = 10000 \ No newline at end of file diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index f464f707..92c5efa8 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -1,13 +1,12 @@ -import { RootLayout } from "@/components/layouts"; import { APPLICATION_ROUTES } from "@/utils"; import { Navigate, RouterProvider, createBrowserRouter, } from "react-router-dom"; -import { ProtectedPage } from "@/app/routes/protected-route"; +import { ProtectedRoute } from "@/app/routes/protected-route"; import { MainErrorFallback } from "@/components/errors"; -import ModelFormsLayout from "@/components/layouts/model-forms-layout"; +import { ModelFormsLayout, RootLayout } from "@/layouts"; const router = createBrowserRouter([ { @@ -92,9 +91,9 @@ const router = createBrowserRouter([ */ { element: ( - + - + ), children: [ /** @@ -255,9 +254,9 @@ const router = createBrowserRouter([ ); return { Component: () => ( - + - + ), }; }, @@ -278,9 +277,9 @@ const router = createBrowserRouter([ ); return { Component: () => ( - + - + ), }; }, diff --git a/frontend/src/app/routes/about.tsx b/frontend/src/app/routes/about.tsx index 2898ad9a..6530ca48 100644 --- a/frontend/src/app/routes/about.tsx +++ b/frontend/src/app/routes/about.tsx @@ -1,9 +1,9 @@ import { Header } from "@/components/shared"; import { Image } from "@/components/ui/image"; -import HOTTeamLandscape from "@/assets/images/hot_team_landscape.png"; import { Head } from "@/components/seo"; -import AIIcon from "@/assets/svgs/fair_ai_icon.svg"; import { aboutPageContent } from "@/constants"; +import { HOTTeamLandscape } from "@/assets/images"; +import { AIIcon } from "@/assets/svgs"; export const AboutPage = () => { return ( diff --git a/frontend/src/app/routes/account/models.tsx b/frontend/src/app/routes/account/models.tsx index cafd222d..8206295b 100644 --- a/frontend/src/app/routes/account/models.tsx +++ b/frontend/src/app/routes/account/models.tsx @@ -1,6 +1,6 @@ -import Pagination, { PAGE_LIMIT } from "@/components/shared/pagination"; +import { Pagination, } from "@/components/shared"; import { Head } from "@/components/seo"; -import { LayoutView } from "@/enums/models"; +import { LayoutView } from "@/enums"; import { LayoutToggle, PageHeader } from "@/features/models/components"; import { MobileModelFiltersDialog } from "@/features/models/components/dialogs"; import { @@ -24,6 +24,7 @@ import ModelNotFound from "@/features/models/components/model-not-found"; import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { useAuth } from "@/app/providers/auth-provider"; import { modelPagesContent } from "@/constants"; +import { PAGE_LIMIT } from "@/components/shared"; export const UserModelsPage = () => { const { isOpened, openDialog, closeDialog } = useDialog(); diff --git a/frontend/src/app/routes/landing.tsx b/frontend/src/app/routes/landing.tsx index f1dab738..134671b1 100644 --- a/frontend/src/app/routes/landing.tsx +++ b/frontend/src/app/routes/landing.tsx @@ -1,12 +1,6 @@ -import { Header } from "@/components/landing/header"; -import WhatIsFAIR from "@/components/landing/about-fair/about-fair"; -import CoreFeatures from "@/components/landing/core-features/core-features"; -import Corevalues from "@/components/landing/core-values/core-values"; -import CallToAction from "@/components/landing/cta/cta"; -import TheFAIRProcess from "@/components/shared/fair-process/fair-process"; import { FAQs } from "@/components/shared"; -import Kpi from "@/components/landing/kpi/kpi"; -import TaglineBanner from "@/components/landing/tagline/tagline"; +import { Header, Kpi, TaglineBanner, TheFAIRProcess, CallToAction, Corevalues, CoreFeatures, WhatIsFAIR } from "@/components/landing"; + import { Head } from "@/components/seo"; export const LandingPage = () => { @@ -19,7 +13,9 @@ export const LandingPage = () => { - +
+ +
diff --git a/frontend/src/app/routes/learn.tsx b/frontend/src/app/routes/learn.tsx index 32e518d8..356cc43e 100644 --- a/frontend/src/app/routes/learn.tsx +++ b/frontend/src/app/routes/learn.tsx @@ -1,16 +1,17 @@ import { TheFAIRProcess } from "@/components/landing"; import { Header, SectionHeader } from "@/components/shared"; import { Image } from "@/components/ui/image"; -import fAIrValues from "@/assets/svgs/fair_values.svg"; + import { ExternalLinkIcon, YouTubePlayCircleIcon } from "@/components/ui/icons"; import { Button } from "@/components/ui/button"; import { Link } from "@/components/ui/link"; import { SHOELACE_SIZES } from "@/enums"; import { Head } from "@/components/seo"; import { useState } from "react"; -import VideoPlaceholderImage from "@/assets/images/header_bg.jpg"; import { learnPageContent } from "@/constants"; import { TGuide, TVideo } from "@/types"; +import { JumbotronBackgroundImage } from "@/assets/images"; +import { fAIrValues } from "@/assets/svgs"; export const LearnPage = () => { return ( @@ -118,7 +119,7 @@ const VideoCard = ({ video }: { video: TVideo }) => {
{video.title} { const { id } = useParams<{ id: string }>(); diff --git a/frontend/src/app/routes/models/models-list.tsx b/frontend/src/app/routes/models/models-list.tsx index 76b7a3d2..0e932e43 100644 --- a/frontend/src/app/routes/models/models-list.tsx +++ b/frontend/src/app/routes/models/models-list.tsx @@ -20,7 +20,7 @@ import { OrderingFilter, SearchFilter, } from "@/features/models/components/filters"; -import Pagination, { PAGE_LIMIT } from "@/components/shared/pagination"; +import { Pagination, PAGE_LIMIT } from "@/components/shared"; import { APP_CONTENT } from "@/utils"; import { PageHeader } from "@/features/models/components/"; import { FeatureCollection } from "@/types"; @@ -28,7 +28,7 @@ import ModelNotFound from "@/features/models/components/model-not-found"; import { useDialog } from "@/hooks/use-dialog"; import { MobileModelFiltersDialog } from "@/features/models/components/dialogs"; import { Head } from "@/components/seo"; -import { LayoutView } from "@/enums/models"; +import { LayoutView } from "@/enums"; import { useScrollToElement, useScrollToTop, @@ -92,7 +92,7 @@ export const ModelsPage = () => { if (mapViewIsActive) { return (
@@ -102,7 +102,7 @@ export const ModelsPage = () => { isError={isError} />
-
+
{modelsMapDataIsPending || modelsMapDataIsError ? (
) : ( @@ -149,8 +149,8 @@ export const ModelsPage = () => {
{/* Filters */} -
-
+
+
@@ -195,7 +195,7 @@ export const ModelsPage = () => { {isPending ? (
) : ( -
+

{data?.count}{" "} diff --git a/frontend/src/app/routes/protected-route.tsx b/frontend/src/app/routes/protected-route.tsx index 48cb6522..2522c498 100644 --- a/frontend/src/app/routes/protected-route.tsx +++ b/frontend/src/app/routes/protected-route.tsx @@ -9,7 +9,7 @@ type ProtectedRouteProps = { children: React.ReactNode; }; -export const ProtectedPage: React.FC = ({ children }) => { +export const ProtectedRoute: React.FC = ({ children }) => { const { isAuthenticated } = useAuth(); const { handleLogin, loading } = useLogin(); diff --git a/frontend/src/assets/images/index.ts b/frontend/src/assets/images/index.ts new file mode 100644 index 00000000..ab6b9253 --- /dev/null +++ b/frontend/src/assets/images/index.ts @@ -0,0 +1,10 @@ +export { default as JumbotronBackgroundImage } from "@/assets/images/header_bg.jpg"; +export { default as CreativeCommonsBadge } from "@/assets/images/cc_by_badge.png"; +export { default as HOTTeamTwo } from "@/assets/images/hot_team_2.jpg"; +export { default as HOTTeamLandscape } from "@/assets/images/hot_team_landscape.png"; +export { default as HOTTeam } from "@/assets/images/hot_team.jpg"; +export { default as MapMarkerIcon } from "@/assets/images/map_marker.png"; +export { default as MapathonOngoing } from "@/assets/images/mapathon_ongoing.jpg"; +export { default as ModelFormConfirmation } from "@/assets/images/model_creation_success.png"; +export { default as FairModelPlaceholderImage } from "@/assets/images/model_placeholder_image.png"; +export { default as TrainingInProgressImage } from "@/assets/images/training_in_progress.png"; \ No newline at end of file diff --git a/frontend/src/assets/svgs/index.ts b/frontend/src/assets/svgs/index.ts new file mode 100644 index 00000000..19c871e2 --- /dev/null +++ b/frontend/src/assets/svgs/index.ts @@ -0,0 +1,7 @@ +export { default as DashedLineConnector } from "@/assets/svgs/dashed_line.svg"; +export { default as AIIcon } from "@/assets/svgs/fair_ai_icon.svg"; +export { default as fAIrValues } from "@/assets/svgs/fair_values.svg"; +export { default as HamburgerIcon } from "@/assets/svgs/hamburger_icon.svg"; +export { default as HOTFairLogo } from "@/assets/svgs/hot_fair_logo.svg"; +export { default as JOSMLogo } from "@/assets/svgs/josm_logo.svg"; +export { default as OSMLogo } from "@/assets/svgs/osm_logo.svg"; \ No newline at end of file diff --git a/frontend/src/components/errors/fallback.tsx b/frontend/src/components/errors/fallback.tsx index d2a767ac..47d20eae 100644 --- a/frontend/src/components/errors/fallback.tsx +++ b/frontend/src/components/errors/fallback.tsx @@ -1,7 +1,7 @@ import { Button } from "@/components/ui/button"; import { APP_CONTENT } from "@/utils"; -const MainErrorFallback = () => { +export const MainErrorFallback = () => { return ( <>

@@ -23,4 +23,3 @@ const MainErrorFallback = () => { ); }; -export default MainErrorFallback; diff --git a/frontend/src/components/errors/index.ts b/frontend/src/components/errors/index.ts index d674f9cd..5bae486d 100644 --- a/frontend/src/components/errors/index.ts +++ b/frontend/src/components/errors/index.ts @@ -1,2 +1,2 @@ -export { default as MainErrorFallback } from "./fallback"; -export { default as PageUnderConstruction } from "./under-construction"; +export { MainErrorFallback } from "./fallback"; +export { PageUnderConstruction } from "./under-construction"; diff --git a/frontend/src/components/errors/under-construction.tsx b/frontend/src/components/errors/under-construction.tsx index 85caf879..fc99ea2e 100644 --- a/frontend/src/components/errors/under-construction.tsx +++ b/frontend/src/components/errors/under-construction.tsx @@ -1,7 +1,7 @@ import { Button } from "@/components/ui/button"; import { APP_CONTENT } from "@/utils"; -const PageUnderConstruction = () => { +export const PageUnderConstruction = () => { return (
@@ -21,4 +21,3 @@ const PageUnderConstruction = () => { ); }; -export default PageUnderConstruction; diff --git a/frontend/src/components/landing/about-fair/about-fair.tsx b/frontend/src/components/landing/about-fair/about-fair.tsx index 6ccb0dc5..bc475696 100644 --- a/frontend/src/components/landing/about-fair/about-fair.tsx +++ b/frontend/src/components/landing/about-fair/about-fair.tsx @@ -1,9 +1,10 @@ -import AIIcon from "@/assets/svgs/fair_ai_icon.svg"; + import styles from "./about-fair.module.css"; import { APP_CONTENT } from "@/utils/content"; import { Image } from "@/components/ui/image"; +import { AIIcon } from "@/assets/svgs"; -const WhatIsFAIR = () => { +export const WhatIsFAIR = () => { return (
@@ -18,4 +19,4 @@ const WhatIsFAIR = () => { ); }; -export default WhatIsFAIR; + diff --git a/frontend/src/components/landing/core-features/core-features.tsx b/frontend/src/components/landing/core-features/core-features.tsx index e1156faf..1abc8763 100644 --- a/frontend/src/components/landing/core-features/core-features.tsx +++ b/frontend/src/components/landing/core-features/core-features.tsx @@ -22,7 +22,7 @@ const coreFeatures: TCoreFeatures[] = [ }, ]; -const CoreFeatures = () => { +export const CoreFeatures = () => { return (
{coreFeatures.map((feature, id) => ( @@ -36,5 +36,3 @@ const CoreFeatures = () => {
); }; - -export default CoreFeatures; diff --git a/frontend/src/components/landing/core-values/core-values.tsx b/frontend/src/components/landing/core-values/core-values.tsx index ac7e0a46..d6068b99 100644 --- a/frontend/src/components/landing/core-values/core-values.tsx +++ b/frontend/src/components/landing/core-values/core-values.tsx @@ -1,11 +1,10 @@ import styles from "./core-values.module.css"; -import HOTTeam from "@/assets/images/hot_team.jpg"; -import MapathonOngoing from "@/assets/images/mapathon_ongoing.jpg"; import { APP_CONTENT } from "@/utils/content"; -import DashedLineConnector from "@/assets/svgs/dashed_line.svg"; import { Image } from "@/components/ui/image"; +import { HOTTeam, MapathonOngoing } from "@/assets/images"; +import { DashedLineConnector } from "@/assets/svgs"; -const Corevalues = () => { +export const Corevalues = () => { return (
@@ -110,4 +109,3 @@ const Corevalues = () => { ); }; -export default Corevalues; diff --git a/frontend/src/components/landing/cta/cta.tsx b/frontend/src/components/landing/cta/cta.tsx index 67d5fd40..919ec53d 100644 --- a/frontend/src/components/landing/cta/cta.tsx +++ b/frontend/src/components/landing/cta/cta.tsx @@ -1,11 +1,11 @@ import { Button } from "@/components/ui/button/"; -import HOTTeam from "@/assets/images/hot_team_2.jpg"; import styles from "./cta.module.css"; import { APP_CONTENT } from "@/utils"; import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; +import { HOTTeamTwo } from "@/assets/images"; -const CallToAction = () => { +export const CallToAction = () => { return (
@@ -28,7 +28,7 @@ const CallToAction = () => {
{APP_CONTENT.homepage.callToAction.ctaButton} @@ -40,4 +40,3 @@ const CallToAction = () => { ); }; -export default CallToAction; diff --git a/frontend/src/components/landing/header/header.tsx b/frontend/src/components/landing/header/header.tsx index f51617e5..21a72203 100644 --- a/frontend/src/components/landing/header/header.tsx +++ b/frontend/src/components/landing/header/header.tsx @@ -6,7 +6,7 @@ import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; import { APPLICATION_ROUTES } from "@/utils"; -const Header = () => { +export const Header = () => { return (
@@ -22,7 +22,6 @@ const Header = () => { nativeAnchor={false} > @@ -50,4 +49,3 @@ const Header = () => { ); }; -export default Header; diff --git a/frontend/src/components/landing/header/index.ts b/frontend/src/components/landing/header/index.ts index 5ed4e585..924cc88d 100644 --- a/frontend/src/components/landing/header/index.ts +++ b/frontend/src/components/landing/header/index.ts @@ -1 +1 @@ -export { default as Header } from "./header"; +export { Header } from "./header"; diff --git a/frontend/src/components/landing/index.ts b/frontend/src/components/landing/index.ts index c2c1f6e5..8ebaf391 100644 --- a/frontend/src/components/landing/index.ts +++ b/frontend/src/components/landing/index.ts @@ -1,7 +1,8 @@ -export { default as Kpi } from "./kpi/kpi"; -export { default as WhatIsFAIR } from "./about-fair/about-fair"; -export { default as TheFAIRProcess } from "../shared/fair-process/fair-process"; -export { default as CoreFeatures } from "./core-features/core-features"; -export { default as Corevalues } from "./core-values/core-values"; -export { default as TaglineBanner } from "./tagline/tagline"; -export { default as CallToAction } from "./cta/cta"; +export { Kpi } from "./kpi/kpi"; +export { WhatIsFAIR } from "./about-fair/about-fair"; +export { TheFAIRProcess } from "../shared/fair-process/fair-process"; +export { CoreFeatures } from "./core-features/core-features"; +export { Corevalues } from "./core-values/core-values"; +export { TaglineBanner } from "./tagline/tagline"; +export { CallToAction } from "./cta/cta"; +export * from './header' \ No newline at end of file diff --git a/frontend/src/components/landing/kpi/kpi.tsx b/frontend/src/components/landing/kpi/kpi.tsx index 617a44da..8cedf868 100644 --- a/frontend/src/components/landing/kpi/kpi.tsx +++ b/frontend/src/components/landing/kpi/kpi.tsx @@ -20,7 +20,7 @@ const fetchKPIStats = async (): Promise => { return data; }; -const Kpi = () => { +export const Kpi = () => { const { data, isLoading, isError, error } = useQuery({ queryKey: ["kpis"], queryFn: fetchKPIStats, @@ -71,4 +71,3 @@ const Kpi = () => { ); }; -export default Kpi; diff --git a/frontend/src/components/landing/tagline/tagline.tsx b/frontend/src/components/landing/tagline/tagline.tsx index a52a2dc6..f16505a4 100644 --- a/frontend/src/components/landing/tagline/tagline.tsx +++ b/frontend/src/components/landing/tagline/tagline.tsx @@ -1,7 +1,7 @@ import { APP_CONTENT } from "@/utils/content"; import styles from "./tagline.module.css"; -const TaglineBanner = () => { +export const TaglineBanner = () => { return (

@@ -17,4 +17,3 @@ const TaglineBanner = () => { ); }; -export default TaglineBanner; diff --git a/frontend/src/components/ui/footer/footer.tsx b/frontend/src/components/layout/footer/footer.tsx similarity index 97% rename from frontend/src/components/ui/footer/footer.tsx rename to frontend/src/components/layout/footer/footer.tsx index 31b532de..1539edb9 100644 --- a/frontend/src/components/ui/footer/footer.tsx +++ b/frontend/src/components/layout/footer/footer.tsx @@ -1,4 +1,4 @@ -import CreativeCommonsBadge from "@/assets/images/cc_by_badge.png"; +import { CreativeCommonsBadge } from "@/assets/images"; import FacebookLogo from "@/assets/svgs/socials/facebook_logo.svg"; import GitHubLogo from "@/assets/svgs/socials/github_logo.svg"; import XLogo from "@/assets/svgs/socials/x_logo.svg"; @@ -35,7 +35,7 @@ const socials = [ logo: InstagramLogo, }, ]; -const Footer = () => { +export const Footer = () => { return (

@@ -124,4 +124,3 @@ const Footer = () => { ); }; -export default Footer; diff --git a/frontend/src/components/layout/footer/index.ts b/frontend/src/components/layout/footer/index.ts new file mode 100644 index 00000000..25eaacd6 --- /dev/null +++ b/frontend/src/components/layout/footer/index.ts @@ -0,0 +1 @@ +export { Footer } from "./footer"; diff --git a/frontend/src/components/layout/index.ts b/frontend/src/components/layout/index.ts new file mode 100644 index 00000000..05494aaa --- /dev/null +++ b/frontend/src/components/layout/index.ts @@ -0,0 +1,2 @@ +export * from './footer' +export * from './navbar' \ No newline at end of file diff --git a/frontend/src/components/layout/navbar/index.ts b/frontend/src/components/layout/navbar/index.ts new file mode 100644 index 00000000..82411f84 --- /dev/null +++ b/frontend/src/components/layout/navbar/index.ts @@ -0,0 +1,3 @@ +export { NavBar } from "./navbar"; +export { UserProfile } from "./user-profile"; +export { NavLogo } from "./nav-logo"; diff --git a/frontend/src/components/ui/navbar/nav-logo.tsx b/frontend/src/components/layout/navbar/nav-logo.tsx similarity index 86% rename from frontend/src/components/ui/navbar/nav-logo.tsx rename to frontend/src/components/layout/navbar/nav-logo.tsx index 7096a6b8..789c5cc7 100644 --- a/frontend/src/components/ui/navbar/nav-logo.tsx +++ b/frontend/src/components/layout/navbar/nav-logo.tsx @@ -1,9 +1,9 @@ import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; import { Image } from "@/components/ui/image"; -import HOTFairLogo from "@/assets/svgs/hot_fair_logo.svg"; import { useNavigate } from "react-router-dom"; +import { HOTFairLogo } from "@/assets/svgs"; -const NavLogo = ({ +export const NavLogo = ({ onClick, width = "125px", height = "72px", @@ -28,4 +28,4 @@ const NavLogo = ({ ); }; -export default NavLogo; + diff --git a/frontend/src/components/ui/navbar/navbar.module.css b/frontend/src/components/layout/navbar/navbar.module.css similarity index 100% rename from frontend/src/components/ui/navbar/navbar.module.css rename to frontend/src/components/layout/navbar/navbar.module.css diff --git a/frontend/src/components/layout/navbar/navbar.tsx b/frontend/src/components/layout/navbar/navbar.tsx new file mode 100644 index 00000000..a970fba9 --- /dev/null +++ b/frontend/src/components/layout/navbar/navbar.tsx @@ -0,0 +1,139 @@ +import { Button } from "@/components/ui/button"; +import styles from "@/components/layout/navbar/navbar.module.css"; +import { Drawer } from "@/components/ui/drawer"; +import { useState } from "react"; +import { Link } from "@/components/ui/link"; +import { Image } from "@/components/ui/image"; +import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; +import { useAuth } from "@/app/providers/auth-provider"; +import { UserProfile } from "@/components/layout"; +import { useLogin } from "@/hooks/use-login"; +import { useLocation } from "react-router-dom"; +import { DrawerPlacements } from "@/enums"; +import { NavLogo } from "@/components/layout"; +import { HamburgerIcon } from "@/assets/svgs"; + +export const NavBar = () => { + const [open, setOpen] = useState(false); + const { isAuthenticated } = useAuth(); + const { handleLogin, loading } = useLogin(); + + return ( + <> + +
+
+ + +
+
+ +
+
+ {isAuthenticated ? ( + + ) : ( + + )} +
+
+
+ + + ); +}; + + + +type TNavBarLinks = { + title: string; + href: string; +}[]; + +const navLinks: TNavBarLinks = [ + { + title: APP_CONTENT.navbar.routes.exploreModels, + href: APPLICATION_ROUTES.MODELS, + }, + { + title: APP_CONTENT.navbar.routes.learn, + href: APPLICATION_ROUTES.LEARN, + }, + { + title: APP_CONTENT.navbar.routes.about, + href: APPLICATION_ROUTES.ABOUT, + }, + { + title: APP_CONTENT.navbar.routes.resources, + href: APPLICATION_ROUTES.RESOURCES, + }, +]; + +type NavBarLinksProps = { + className: string; + setOpen?: (arg: boolean) => void; +}; + +const NavBarLinks: React.FC = ({ className, setOpen }) => { + const location = useLocation(); + + return ( +
    + {navLinks.map((link, id) => ( +
  • { + //close the drawer after navigating to a new page on mobile + setOpen && setOpen(false); + }} + className={`${styles.navLinkItem} ${location.pathname.includes(link.href) && styles.activeLink}`} + > + + {link.title} + +
  • + ))} +
+ ); +}; diff --git a/frontend/src/components/ui/navbar/user-profile.tsx b/frontend/src/components/layout/navbar/user-profile.tsx similarity index 91% rename from frontend/src/components/ui/navbar/user-profile.tsx rename to frontend/src/components/layout/navbar/user-profile.tsx index c21e351a..b4317334 100644 --- a/frontend/src/components/ui/navbar/user-profile.tsx +++ b/frontend/src/components/layout/navbar/user-profile.tsx @@ -1,4 +1,4 @@ -import styles from "@/components/ui/navbar/navbar.module.css"; +import styles from "@/components/layout/navbar/navbar.module.css"; import SlAvatar from "@shoelace-style/shoelace/dist/react/avatar/index.js"; import { DropDown } from "@/components/ui/dropdown"; import { useNavigate } from "react-router-dom"; @@ -6,7 +6,7 @@ import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { useAuth } from "@/app/providers/auth-provider"; -const UserProfile = ({ hideFullName }: { hideFullName?: boolean }) => { +export const UserProfile = ({ hideFullName }: { hideFullName?: boolean }) => { const { user, logout } = useAuth(); const { onDropdownHide, onDropdownShow, dropdownIsOpened } = @@ -61,4 +61,4 @@ const UserProfile = ({ hideFullName }: { hideFullName?: boolean }) => { ); }; -export default UserProfile; + diff --git a/frontend/src/components/layouts/index.ts b/frontend/src/components/layouts/index.ts deleted file mode 100644 index 59957d1e..00000000 --- a/frontend/src/components/layouts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as RootLayout } from "./root-layout"; diff --git a/frontend/src/components/map/setups/setup-maplibre.ts b/frontend/src/components/map/setups/setup-maplibre.ts index 8d2070f4..66367518 100644 --- a/frontend/src/components/map/setups/setup-maplibre.ts +++ b/frontend/src/components/map/setups/setup-maplibre.ts @@ -5,6 +5,7 @@ import { Protocol } from "pmtiles"; export const setupMaplibreMap = ( containerRef: React.RefObject, style: StyleSpecification | string, + pmtiles: boolean ): Map => { // Check if RTL plugin is needed and set it if (maplibregl.getRTLTextPluginStatus() === "unavailable") { @@ -14,15 +15,33 @@ export const setupMaplibreMap = ( ); } - let protocol = new Protocol(); - maplibregl.addProtocol("pmtiles", protocol.tile); + if (pmtiles) { + let protocol = new Protocol(); + maplibregl.addProtocol("pmtiles", protocol.tile); + } - return new maplibregl.Map({ + const map = new maplibregl.Map({ container: containerRef.current!, style: style, center: [0, 0], zoom: 0.5, minZoom: 1, maxZoom: MAX_ZOOM_LEVEL, + pitchWithRotate: false + }) + + // Prevent the map from rotating + map.on('rotatestart', () => { + map.setBearing(0); + }); + + map.on('rotate', () => { + map.setBearing(0); }); + + map.on('rotateend', () => { + map.setBearing(0); + }); + + return map }; diff --git a/frontend/src/components/shared/fair-process/fair-process.tsx b/frontend/src/components/shared/fair-process/fair-process.tsx index c3d614d1..61bacfd3 100644 --- a/frontend/src/components/shared/fair-process/fair-process.tsx +++ b/frontend/src/components/shared/fair-process/fair-process.tsx @@ -47,7 +47,7 @@ const steps: TSteps[] = [ }, ]; -const TheFAIRProcess = ({ +export const TheFAIRProcess = ({ disableStyle = false, }: { disableStyle?: boolean; @@ -157,4 +157,4 @@ const TheFAIRProcess = ({ ); }; -export default TheFAIRProcess; + diff --git a/frontend/src/components/shared/faqs/faqs.module.css b/frontend/src/components/shared/faqs/faqs.module.css index 4379cac7..227665a6 100644 --- a/frontend/src/components/shared/faqs/faqs.module.css +++ b/frontend/src/components/shared/faqs/faqs.module.css @@ -1,7 +1,3 @@ -.FAQS { - padding: 0 var(--sl-spacing-large); -} - .heading { width: auto; text-align: center; @@ -31,9 +27,6 @@ /* md: */ @media (min-width: 768px) { - .FAQS { - padding: 0 var(--hot-fair-spacing-extra-large); - } .heading { max-width: 218px; diff --git a/frontend/src/components/shared/index.ts b/frontend/src/components/shared/index.ts index 1ac79a69..b981988b 100644 --- a/frontend/src/components/shared/index.ts +++ b/frontend/src/components/shared/index.ts @@ -1,3 +1,5 @@ -export { Header } from "./header/header"; +export { Header } from "./static-page-header/header"; export { FAQs } from "./faqs/faqs"; -export { SectionHeader } from "./section-header/section-header"; +export { SectionHeader } from "./section-header"; +export * from './pagination' +export { TheFAIRProcess } from './fair-process/fair-process' \ No newline at end of file diff --git a/frontend/src/components/shared/pagination.tsx b/frontend/src/components/shared/pagination.tsx index 2d7ac0c1..ab45dad2 100644 --- a/frontend/src/components/shared/pagination.tsx +++ b/frontend/src/components/shared/pagination.tsx @@ -21,7 +21,7 @@ type PaginationProps = { centerOnMobile?: boolean; }; -const Pagination: React.FC = ({ +export const Pagination: React.FC = ({ hasNextPage, hasPrevPage, disableNextPage, @@ -110,4 +110,4 @@ const Pagination: React.FC = ({ ); }; -export default Pagination; + diff --git a/frontend/src/components/shared/section-header/section-header.tsx b/frontend/src/components/shared/section-header.tsx similarity index 100% rename from frontend/src/components/shared/section-header/section-header.tsx rename to frontend/src/components/shared/section-header.tsx diff --git a/frontend/src/components/shared/header/header.module.css b/frontend/src/components/shared/static-page-header/header.module.css similarity index 52% rename from frontend/src/components/shared/header/header.module.css rename to frontend/src/components/shared/static-page-header/header.module.css index 0e639ba1..35efdb7d 100644 --- a/frontend/src/components/shared/header/header.module.css +++ b/frontend/src/components/shared/static-page-header/header.module.css @@ -1,3 +1,6 @@ .headerBackground { background-image: url("../../../src/assets/svgs/header_bg_contour.svg"); + background-size: cover; + background-position: center; + background-repeat: no-repeat; } diff --git a/frontend/src/components/shared/header/header.tsx b/frontend/src/components/shared/static-page-header/header.tsx similarity index 85% rename from frontend/src/components/shared/header/header.tsx rename to frontend/src/components/shared/static-page-header/header.tsx index 67a92d2f..436e7fcc 100644 --- a/frontend/src/components/shared/header/header.tsx +++ b/frontend/src/components/shared/static-page-header/header.tsx @@ -4,7 +4,7 @@ import styles from "./header.module.css"; export const Header = React.memo(({ title }: { title: string }) => { return (
{title} diff --git a/frontend/src/components/ui/footer/index.ts b/frontend/src/components/ui/footer/index.ts deleted file mode 100644 index a357e420..00000000 --- a/frontend/src/components/ui/footer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Footer } from "./footer"; diff --git a/frontend/src/components/ui/icons/index.ts b/frontend/src/components/ui/icons/index.ts index 761e1c26..e631e296 100644 --- a/frontend/src/components/ui/icons/index.ts +++ b/frontend/src/components/ui/icons/index.ts @@ -54,3 +54,4 @@ export { BookTemplateIcon } from "./book-template-icon"; export { YouTubePlayCircleIcon } from "./youtube-play-circle-icon"; export { CheckIcon } from "./check-icon"; export { DesktopFlowIcon } from "./desktop-flow-icon"; +export { NoTrainingAreaIcon } from './no-training-area-icon' \ No newline at end of file diff --git a/frontend/src/components/ui/icons/no-training-area-icon.tsx b/frontend/src/components/ui/icons/no-training-area-icon.tsx new file mode 100644 index 00000000..4fb52f63 --- /dev/null +++ b/frontend/src/components/ui/icons/no-training-area-icon.tsx @@ -0,0 +1,36 @@ +import { IconProps } from "@/types" + +export const NoTrainingAreaIcon: React.FC = (props) => ( + + + + + +) diff --git a/frontend/src/components/ui/navbar/index.ts b/frontend/src/components/ui/navbar/index.ts deleted file mode 100644 index e64cf5f4..00000000 --- a/frontend/src/components/ui/navbar/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as NavBar } from "./navbar"; -export { default as UserProfile } from "./user-profile"; -export { default as NavLogo } from "./nav-logo"; diff --git a/frontend/src/components/ui/navbar/navbar.tsx b/frontend/src/components/ui/navbar/navbar.tsx index fe2448c0..cadfbe7a 100644 --- a/frontend/src/components/ui/navbar/navbar.tsx +++ b/frontend/src/components/ui/navbar/navbar.tsx @@ -1,19 +1,19 @@ import { Button } from "@/components/ui/button"; -import styles from "@/components/ui/navbar/navbar.module.css"; -import HamburgerIcon from "@/assets/svgs/hamburger_icon.svg"; +import styles from "@/components/layouts/navbar/navbar.module.css"; import { Drawer } from "@/components/ui/drawer"; import { useState } from "react"; import { Link } from "@/components/ui/link"; import { Image } from "@/components/ui/image"; import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; import { useAuth } from "@/app/providers/auth-provider"; -import UserProfile from "@/components/ui/navbar/user-profile"; +import { UserProfile } from "@/components/layout"; import { useLogin } from "@/hooks/use-login"; import { useLocation } from "react-router-dom"; -import NavLogo from "./nav-logo"; import { DrawerPlacements } from "@/enums"; +import { NavLogo } from "@/components/layout"; +import { HamburgerIcon } from "@/assets/svgs"; -const NavBar = () => { +export const NavBar = () => { const [open, setOpen] = useState(false); const { isAuthenticated } = useAuth(); const { handleLogin, loading } = useLogin(); @@ -84,7 +84,7 @@ const NavBar = () => { ); }; -export default NavBar; + type TNavBarLinks = { title: string; diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index 007e5180..76bcbe96 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -193,4 +193,21 @@ export const ENVS = { */ JOSM_REMOTE_URL: import.meta.env.VITE_JOSM_REMOTE_URL, + + /** + # The time to poll the backend for the status of the AOI training labels fetching, in milliseconds (ms). + # Data type: Positive Integer (e.g., 900). + # Default value: 5000 milliseconds (5 seconds). + */ + TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS: import.meta.env.VITE_TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS, + + /** + # The time to poll the backend for the status of the OSM last updated time, in milliseconds (ms). + # Data type: Positive Integer (e.g., 900). + # Default value: 10000 milliseconds (10 seconds). + */ + + OSM_LAST_UPDATED_POOLING_INTERVAL_MS: import.meta.env.VITE_OSM_LAST_UPDATED_POOLING_INTERVAL_MS }; + + diff --git a/frontend/src/enums/index.ts b/frontend/src/enums/index.ts index d7fa659a..194f7a87 100644 --- a/frontend/src/enums/index.ts +++ b/frontend/src/enums/index.ts @@ -1,2 +1,3 @@ export * from "./common"; export * from "./map"; +export * from './models' \ No newline at end of file diff --git a/frontend/src/enums/training-area.ts b/frontend/src/enums/training-area.ts new file mode 100644 index 00000000..15613db4 --- /dev/null +++ b/frontend/src/enums/training-area.ts @@ -0,0 +1,5 @@ +export enum LabelStatus { + DOWNLOADED = 1, + NOT_DOWNLOADED = -1, + DOWNLOADING = 0 +} diff --git a/frontend/src/features/model-creation/api/factory.ts b/frontend/src/features/model-creation/api/factory.ts index c5401c41..5241dad2 100644 --- a/frontend/src/features/model-creation/api/factory.ts +++ b/frontend/src/features/model-creation/api/factory.ts @@ -1,5 +1,6 @@ import { keepPreviousData, queryOptions } from "@tanstack/react-query"; import { + getTrainingArea, getTrainingAreaLabels, getTrainingAreas, getTrainingDatasetLabels, @@ -60,3 +61,12 @@ export const getTrainingAreaLabelsQueryOptions = (aoiId: number) => { queryFn: () => getTrainingAreaLabels(aoiId), }); }; + + +export const getTrainingAreaQueryOptions = (aoiId: number) => { + return queryOptions({ + queryKey: ["training-area", aoiId], + queryFn: () => getTrainingArea(aoiId), + staleTime: 0, + }); +}; diff --git a/frontend/src/features/model-creation/api/get-trainings.ts b/frontend/src/features/model-creation/api/get-trainings.ts index 47ca9b9a..c7b89141 100644 --- a/frontend/src/features/model-creation/api/get-trainings.ts +++ b/frontend/src/features/model-creation/api/get-trainings.ts @@ -2,6 +2,8 @@ import { API_ENDPOINTS, apiClient } from "@/services"; import { FeatureCollection, PaginatedTrainingArea, + + TTrainingAreaFeature, TTrainingDataset, } from "@/types"; @@ -48,3 +50,14 @@ export const getTrainingAreaLabels = async ( ); return res.data; }; + + + +export const getTrainingArea = async ( + aoiId: number, +): Promise => { + const res = await apiClient.get( + API_ENDPOINTS.GET_TRAINING_AREA(aoiId), + ); + return res.data; +}; diff --git a/frontend/src/features/model-creation/components/progress-bar.tsx b/frontend/src/features/model-creation/components/progress-bar.tsx index 35a91864..1615f153 100644 --- a/frontend/src/features/model-creation/components/progress-bar.tsx +++ b/frontend/src/features/model-creation/components/progress-bar.tsx @@ -1,7 +1,7 @@ import { useModelsContext } from "@/app/providers/models-provider"; import { CheckIcon } from "@/components/ui/icons"; import { cn } from "@/utils"; -import { memo } from "react"; +import { memo, useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; type ProgressBarProps = { @@ -14,14 +14,31 @@ const ProgressBar: React.FC = memo( ({ currentPath, currentPageIndex, pages }) => { const navigate = useNavigate(); const { getFullPath, isEditMode } = useModelsContext(); + const activeStepRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + if (activeStepRef.current && containerRef.current) { + const container = containerRef.current; + const activeStep = activeStepRef.current; + + const offset = activeStep.offsetLeft - (container.offsetWidth / 2) + (activeStep.offsetWidth / 2); + container.scrollTo({ + left: offset, + behavior: 'smooth', + }); + } + }, [currentPath]); + return ( -
+
{pages.map((step, index) => { const activeStep = currentPath.includes(step.path); const isLastPage = index === pages.length - 1; return ( - + @@ -295,6 +407,7 @@ const TrainingAreaItem: React.FC<
); -}; +}); + -export default TrainingAreaItem; +export default TrainingAreaItem; \ No newline at end of file diff --git a/frontend/src/features/model-creation/components/training-area/training-area-list.tsx b/frontend/src/features/model-creation/components/training-area/training-area-list.tsx index 38a3283e..0a30821d 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-list.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-list.tsx @@ -1,11 +1,12 @@ import TrainingAreaItem from "@/features/model-creation/components/training-area/training-area-item"; -import Pagination from "@/components/shared/pagination"; +import { Pagination } from "@/components/shared"; import { PaginatedTrainingArea } from "@/types"; import { Dispatch, SetStateAction, useMemo } from "react"; -import { formatDuration, MODEL_CREATION_CONTENT } from "@/utils"; +import { formatDuration, MODEL_CREATION_CONTENT, OSM_LAST_UPDATED_POOLING_INTERVAL_MS } from "@/utils"; import { useQuery } from "@tanstack/react-query"; import { fetchOSMDatabaseLastUpdated } from "@/features/model-creation/hooks/use-training-areas"; import { Map } from "maplibre-gl"; +import { NoTrainingAreaIcon } from "@/components/ui/icons"; const TrainingAreaList = ({ offset, @@ -31,7 +32,7 @@ const TrainingAreaList = ({ } = useQuery({ queryKey: ["osm-database-last-updated"], queryFn: fetchOSMDatabaseLastUpdated, - refetchInterval: 5000, + refetchInterval: OSM_LAST_UPDATED_POOLING_INTERVAL_MS, }); const OSMLastUpdated = useMemo(() => { @@ -84,37 +85,7 @@ const TrainingAreaList = ({
{data?.count === 0 ? (
- - - - - +

No Training Area (TA) added yet. Start by drawing a TA on the map or upload a TA from your device. @@ -124,10 +95,10 @@ const TrainingAreaList = ({

) : (
- {data?.results.features.map((ta, id) => ( + {data?.results.features.sort((a, b) => b.id - a.id).map((ta) => ( void; + stopPolling: () => void; + error: boolean; +} + +export const usePolling = ( + taskId: number, + fetchTaskStatus: (id: number) => Promise, + interval: number = 5000 +): PollingHookReturn => { + const [status, setStatus] = useState(LabelStatus.NOT_DOWNLOADED); + const [isPolling, setIsPolling] = useState(false); + const [error, setError] = useState(false); + const timeoutRef = useRef(null); + + const startPolling = () => { + setIsPolling(true); + setError(false); + pollTask(); + }; + + const pollTask = async () => { + try { + const res = await fetchTaskStatus(taskId); + + if (res?.properties?.label_status === LabelStatus.DOWNLOADED) { + setStatus(LabelStatus.DOWNLOADED); + stopPolling(); + } else if (res?.properties?.label_status === LabelStatus.NOT_DOWNLOADED) { + setStatus(LabelStatus.NOT_DOWNLOADED); + retryPolling(); + } + } catch (err) { + setError(true); + stopPolling(); + } + }; + + const retryPolling = () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = window.setTimeout(pollTask, interval); + }; + + const stopPolling = () => { + setIsPolling(false); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + return { status, isPolling, startPolling, stopPolling, error }; +}; diff --git a/frontend/src/features/model-creation/hooks/use-training-areas.ts b/frontend/src/features/model-creation/hooks/use-training-areas.ts index 01145f93..fb931c43 100644 --- a/frontend/src/features/model-creation/hooks/use-training-areas.ts +++ b/frontend/src/features/model-creation/hooks/use-training-areas.ts @@ -1,6 +1,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { getTrainingAreaLabelsQueryOptions, + getTrainingAreaQueryOptions, getTrainingAreasQueryOptions, getTrainingDatasetLabelsQueryOptions, } from "@/features/model-creation/api/factory"; @@ -21,7 +22,7 @@ export const useGetTrainingAreas = (datasetId: number, offset: number) => { return useQuery({ ...getTrainingAreasQueryOptions(datasetId, offset), //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, + throwOnError: (error) => error?.response?.status >= 500 }); }; @@ -153,6 +154,17 @@ export const useGetTrainingAreaLabels = (aoiId: number, enabled: boolean) => { }); }; + +export const useGetTrainingArea = (aoiId: number, enabled: boolean, refetchInterval: number) => { + return useQuery({ + ...getTrainingAreaQueryOptions(aoiId), + //@ts-expect-error bad type definition + throwOnError: (error) => error?.response?.status >= 500, + enabled: enabled, + refetchInterval: refetchInterval, + }); +}; + type TOSMDatabaseResponse = { lastUpdated: string; }; diff --git a/frontend/src/features/models/api/update-trainings.ts b/frontend/src/features/models/api/update-trainings.ts index 3c7c3bb2..f1815d67 100644 --- a/frontend/src/features/models/api/update-trainings.ts +++ b/frontend/src/features/models/api/update-trainings.ts @@ -2,7 +2,7 @@ import { API_ENDPOINTS, apiClient, MutationConfig } from "@/services"; import { useTrainingHistory } from "@/features/models/hooks/use-training"; import { useModelDetails } from "@/features/models/hooks/use-models"; import { useMutation } from "@tanstack/react-query"; -import { PAGE_LIMIT } from "@/components/shared/pagination"; +import { PAGE_LIMIT } from "@/components/shared"; export const updateTraining = (trainingId: number) => { return apiClient.post(`${API_ENDPOINTS.UPDATE_TRAINING(trainingId)}`); diff --git a/frontend/src/features/models/components/dialogs/training-settings-dialog.tsx b/frontend/src/features/models/components/dialogs/training-settings-dialog.tsx index 6fffddf5..eebe0773 100644 --- a/frontend/src/features/models/components/dialogs/training-settings-dialog.tsx +++ b/frontend/src/features/models/components/dialogs/training-settings-dialog.tsx @@ -9,7 +9,7 @@ import { useModelsContext, } from "@/app/providers/models-provider"; import { useEffect } from "react"; -import { PAGE_LIMIT } from "@/components/shared/pagination"; +import { PAGE_LIMIT } from "@/components/shared"; import { useTrainingHistory } from "@/features/models/hooks/use-training"; type ModelEnhancementDialogProps = { diff --git a/frontend/src/features/models/components/layout-toggle.tsx b/frontend/src/features/models/components/layout-toggle.tsx index a4f06d5d..32beb59f 100644 --- a/frontend/src/features/models/components/layout-toggle.tsx +++ b/frontend/src/features/models/components/layout-toggle.tsx @@ -1,6 +1,6 @@ import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { CategoryIcon, ListIcon } from "@/components/ui/icons"; -import { LayoutView } from "@/enums/models"; +import { LayoutView } from "@/enums"; import { TQueryParams } from "@/types"; const LayoutToggle = ({ diff --git a/frontend/src/features/models/components/map-toggle.tsx b/frontend/src/features/models/components/map-toggle.tsx index 1eb3c02a..2dd7b591 100644 --- a/frontend/src/features/models/components/map-toggle.tsx +++ b/frontend/src/features/models/components/map-toggle.tsx @@ -1,6 +1,6 @@ import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { Switch } from "@/components/ui/form"; -import { LayoutView } from "@/enums/models"; +import { LayoutView } from "@/enums"; import { TQueryParams } from "@/types"; import { APP_CONTENT } from "@/utils"; diff --git a/frontend/src/features/models/components/maps/models-map.tsx b/frontend/src/features/models/components/maps/models-map.tsx index 92ee4d3b..f54cec26 100644 --- a/frontend/src/features/models/components/maps/models-map.tsx +++ b/frontend/src/features/models/components/maps/models-map.tsx @@ -1,17 +1,17 @@ import { Map } from "maplibre-gl"; import { useCallback, useEffect } from "react"; -import mapMarker from "@/assets/images/map_marker.png"; import { MapComponent } from "@/components/map"; import { FeatureCollection, TQueryParams } from "@/types"; import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { useMapInstance } from "@/hooks/use-map-instance"; +import { MapMarkerIcon } from "@/assets/images"; const mapSourceName = "models"; // Font from OpenFreeMap const licensedFonts = ["Noto Sans Regular"]; let markerIcon = new Image(17, 20); -markerIcon.src = mapMarker; +markerIcon.src = MapMarkerIcon; const maplibreLayerDefn = ( map: Map, diff --git a/frontend/src/features/models/components/maps/training-area-map.tsx b/frontend/src/features/models/components/maps/training-area-map.tsx index 36b95f47..88780030 100644 --- a/frontend/src/features/models/components/maps/training-area-map.tsx +++ b/frontend/src/features/models/components/maps/training-area-map.tsx @@ -68,7 +68,7 @@ export const TrainingAreaMap = ({ tmsURL: string; visible: boolean; }) => { - const { mapContainerRef, map, currentZoom } = useMapInstance(); + const { mapContainerRef, map, currentZoom } = useMapInstance(true); const [vectorLayers, setVectorLayers] = useState([]); @@ -182,15 +182,15 @@ export const TrainingAreaMap = ({ ${Object.entries(feature.properties) - .map( - ([key, value]) => ` + .map( + ([key, value]) => ` `, - ) - .join("")} + ) + .join("")}
${key} ${typeof value === "boolean" ? JSON.stringify(value) : value}
diff --git a/frontend/src/features/models/components/model-card.tsx b/frontend/src/features/models/components/model-card.tsx index abd222e7..82dbea70 100644 --- a/frontend/src/features/models/components/model-card.tsx +++ b/frontend/src/features/models/components/model-card.tsx @@ -1,5 +1,4 @@ import { TBadgeVariants, TModel } from "@/types"; -import FairModelPlaceholderImage from "@/assets/images/model_placeholder_image.png"; import { Image } from "@/components/ui/image"; import { APP_CONTENT, APPLICATION_ROUTES, extractDatePart } from "@/utils"; import { Link } from "@/components/ui/link"; @@ -7,6 +6,7 @@ import { truncateString } from "@/utils"; import { roundNumber } from "@/utils/number-utils"; import { useLocation } from "react-router-dom"; import { Badge } from "@/components/ui/badge"; +import { FairModelPlaceholderImage } from "@/assets/images"; type ModelCardProps = { model: TModel; diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index 42e8a9a8..988211c6 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -47,7 +47,7 @@ const ModelDetailsPopUp = ({ > { -
+
{!model && isError ? (
{startMappingPageContent.modelDetails.error}
) : ( diff --git a/frontend/src/features/models/components/training-history-table.tsx b/frontend/src/features/models/components/training-history-table.tsx index bfb55949..e35fb61a 100644 --- a/frontend/src/features/models/components/training-history-table.tsx +++ b/frontend/src/features/models/components/training-history-table.tsx @@ -22,9 +22,10 @@ import { ElipsisIcon, InfoIcon } from "@/components/ui/icons"; import { useDialog } from "@/hooks/use-dialog"; import { TrainingDetailsDialog } from "@/features/models/components/dialogs"; import { useUpdateTraining } from "@/features/models/api/update-trainings"; -import Pagination, { PAGE_LIMIT } from "@/components/shared/pagination"; +import { Pagination, PAGE_LIMIT } from "@/components/shared"; import { useToastNotification } from "@/hooks/use-toast-notification"; + type TrainingHistoryTableProps = { modelId: string; trainingId: number; @@ -42,121 +43,121 @@ const columnDefinitions = ( handleTrainingModal: (trainingId: number) => void, publishTraining: (trainingId: number) => void, ): ColumnDef[] => [ - { - accessorKey: "id", - header: ({ column }) => , - }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader - .epochAndBatchSize, - accessorFn: (row) => `${row.epochs}/${row.batch_size}`, - cell: (row) => ( - {row.getValue() as string} - ), - }, - { - accessorKey: "started_at", - accessorFn: (row) => - row.started_at !== null ? formatDate(row.started_at) : "-", - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.startedAt, - cell: (row) => { - return {row.getValue() as string}; + { + accessorKey: "id", + header: ({ column }) => , }, - }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.duration, - accessorFn: (row) => - row.finished_at && row.started_at - ? formatDuration(new Date(row.started_at), new Date(row.finished_at)) - : "-", - cell: (row) => ( - {row.getValue() as string} - ), - }, - { - accessorKey: "user.username", - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader - .sumittedBy, - cell: ({ row }) => { - return {truncateString(row.original.user.username)}; + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader + .epochAndBatchSize, + accessorFn: (row) => `${row.epochs}/${row.batch_size}`, + cell: (row) => ( + {row.getValue() as string} + ), }, - }, - { - accessorKey: "chips_length", - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.dsSize, - cell: ({ row }) => { - return {row.getValue("chips_length") ?? 0}; + { + accessorKey: "started_at", + accessorFn: (row) => + row.started_at !== null ? formatDate(row.started_at) : "-", + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.startedAt, + cell: (row) => { + return {row.getValue() as string}; + }, }, - }, - { - accessorKey: "accuracy", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return ( - - {Number(row.getValue("accuracy")) > 0 - ? roundNumber(row.getValue("accuracy") ?? 0) - : "-"} - - ); + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.duration, + accessorFn: (row) => + row.finished_at && row.started_at + ? formatDuration(new Date(row.started_at), new Date(row.finished_at)) + : "-", + cell: (row) => ( + {row.getValue() as string} + ), + }, + { + accessorKey: "user.username", + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader + .sumittedBy, + cell: ({ row }) => { + return {truncateString(row.original.user.username)}; + }, }, - }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.status, - accessorKey: "status", - cell: (row) => { - const statusToVariant: Record = { - finished: "green", - failed: "red", - submitted: "blue", - running: "yellow", - }; + { + accessorKey: "chips_length", + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.dsSize, + cell: ({ row }) => { + return {row.getValue("chips_length") ?? 0}; + }, + }, + { + accessorKey: "accuracy", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( + + {Number(row.getValue("accuracy")) > 0 + ? roundNumber(row.getValue("accuracy") ?? 0) + : "-"} + + ); + }, + }, + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.status, + accessorKey: "status", + cell: (row) => { + const statusToVariant: Record = { + finished: "green", + failed: "red", + submitted: "blue", + running: "yellow", + }; - return ( - - {String(row.getValue()).toLocaleLowerCase() as string} - - ); + ] + } + > + {String(row.getValue()).toLocaleLowerCase() as string} + + ); + }, }, - }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.inUse, + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.inUse, - cell: ({ row }) => { - return ( - - {row.getValue("id") === trainingId ? ( - - - - ) : null} - - ); + cell: ({ row }) => { + return ( + + {row.getValue("id") === trainingId ? ( + + + + ) : null} + + ); + }, }, - }, - ...(modelOwner !== authUsername - ? [ + ...(modelOwner !== authUsername + ? [ { header: APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader @@ -175,9 +176,9 @@ const columnDefinitions = ( }, }, ] - : []), - ...(modelOwner === authUsername && isAuthenticated - ? [ + : []), + ...(modelOwner === authUsername && isAuthenticated + ? [ { header: APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader @@ -226,8 +227,8 @@ const columnDefinitions = ( }, }, ] - : []), -]; + : []), + ]; const TrainingHistoryTable: React.FC = ({ trainingId, diff --git a/frontend/src/features/models/hooks/use-models.ts b/frontend/src/features/models/hooks/use-models.ts index 607c5054..49506ad4 100644 --- a/frontend/src/features/models/hooks/use-models.ts +++ b/frontend/src/features/models/hooks/use-models.ts @@ -10,10 +10,10 @@ import { ORDERING_FIELDS } from "@/features/models/components/filters/ordering-f import { TQueryParams } from "@/types"; import { useCallback, useEffect, useMemo, useState } from "react"; import { buildDateFilterQueryString } from "@/utils"; -import { PAGE_LIMIT } from "@/components/shared/pagination"; +import { PAGE_LIMIT } from "@/components/shared"; import { dateFilters } from "@/features/models/components/filters/date-range-filter"; import useDebounce from "@/hooks/use-debounce"; -import { LayoutView } from "@/enums/models"; +import { LayoutView } from "@/enums"; type UseModelsOptions = { limit: number; diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index c125fecc..9f6d0d1e 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -26,7 +26,7 @@ import { TQueryParams } from "@/app/routes/start-mapping"; import ModelAction from "./model-action"; import { TModelPredictionsConfig } from "../api/get-model-predictions"; import { SHOELACE_SIZES } from "@/enums"; -import { NavLogo, UserProfile } from "@/components/ui/navbar"; +import { NavLogo, UserProfile } from "@/components/layout"; import { useNavigate } from "react-router-dom"; import { startMappingPageContent } from "@/constants"; import { Map } from "maplibre-gl"; diff --git a/frontend/src/hooks/use-map-instance.ts b/frontend/src/hooks/use-map-instance.ts index 88226112..a55a9493 100644 --- a/frontend/src/hooks/use-map-instance.ts +++ b/frontend/src/hooks/use-map-instance.ts @@ -5,7 +5,14 @@ import { MAP_STYLES } from "@/utils"; import { Map } from "maplibre-gl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -export const useMapInstance = () => { + +/** + * useMapInstance - Initializes and manages a MapLibre map instance with TerraDraw integration. + * + * @param {boolean} pmtiles - Optional flag to enable PMTiles support. + * @returns {Object} - Contains map instance, zoom level, drawing mode, and container ref. + */ +export const useMapInstance = (pmtiles: boolean = false) => { const mapContainerRef = useRef(null); const [map, setMap] = useState(null); const [currentZoom, setCurrentZoom] = useState(0); @@ -14,7 +21,7 @@ export const useMapInstance = () => { ); useEffect(() => { - const map = setupMaplibreMap(mapContainerRef, MAP_STYLES[BASEMAPS.OSM]); + const map = setupMaplibreMap(mapContainerRef, MAP_STYLES[BASEMAPS.OSM], pmtiles); map.on("load", () => { setMap(map); diff --git a/frontend/src/hooks/use-scroll-to-element.ts b/frontend/src/hooks/use-scroll-to-element.ts index c7472e74..bb2f85b3 100644 --- a/frontend/src/hooks/use-scroll-to-element.ts +++ b/frontend/src/hooks/use-scroll-to-element.ts @@ -16,7 +16,7 @@ export const useScrollToElement = (id: string) => { const scrollToElement = () => { const element = document.getElementById(id); if (element) { - element.scrollIntoView({ behavior: "smooth", block: "center" }); + element.scrollIntoView({ behavior: "smooth", block: "center", }); } }; return { scrollToElement }; diff --git a/frontend/src/layouts/index.ts b/frontend/src/layouts/index.ts new file mode 100644 index 00000000..eeb2bc93 --- /dev/null +++ b/frontend/src/layouts/index.ts @@ -0,0 +1,2 @@ +export { RootLayout } from "./root-layout"; +export { ModelFormsLayout } from "./model-forms-layout"; diff --git a/frontend/src/components/layouts/model-forms-layout.tsx b/frontend/src/layouts/model-forms-layout.tsx similarity index 78% rename from frontend/src/components/layouts/model-forms-layout.tsx rename to frontend/src/layouts/model-forms-layout.tsx index 8a2b0f2b..219dc874 100644 --- a/frontend/src/components/layouts/model-forms-layout.tsx +++ b/frontend/src/layouts/model-forms-layout.tsx @@ -18,7 +18,7 @@ import { ModelsProvider, useModelsContext, } from "@/app/providers/models-provider"; -import { BackButton } from "../ui/button"; +import { BackButton } from "../components/ui/button"; const pages: { id: number; @@ -26,45 +26,45 @@ const pages: { icon: React.ElementType; path: string; }[] = [ - { - id: 1, - title: MODEL_CREATION_CONTENT.progressStepper.modelDetails, - icon: TagsIcon, - path: MODELS_ROUTES.DETAILS, - }, - { - id: 2, - title: MODEL_CREATION_CONTENT.progressStepper.trainingDataset, - icon: DatabaseIcon, - path: MODELS_ROUTES.TRAINING_DATASET, - }, - { - id: 3, - title: MODEL_CREATION_CONTENT.progressStepper.trainingArea, - icon: SquareShadowIcon, - path: MODELS_ROUTES.TRAINING_AREA, - }, - { - id: 4, - title: MODEL_CREATION_CONTENT.progressStepper.trainingSettings, - icon: SettingsIcon, - path: MODELS_ROUTES.TRAINING_SETTINGS, - }, - { - id: 5, - title: MODEL_CREATION_CONTENT.progressStepper.submitModel, - icon: CloudIcon, - path: MODELS_ROUTES.MODEL_SUMMARY, - }, - { - id: 6, - title: MODEL_CREATION_CONTENT.progressStepper.confirmation, - icon: StarIcon, - path: MODELS_ROUTES.CONFIRMATION, - }, -]; + { + id: 1, + title: MODEL_CREATION_CONTENT.progressStepper.modelDetails, + icon: TagsIcon, + path: MODELS_ROUTES.DETAILS, + }, + { + id: 2, + title: MODEL_CREATION_CONTENT.progressStepper.trainingDataset, + icon: DatabaseIcon, + path: MODELS_ROUTES.TRAINING_DATASET, + }, + { + id: 3, + title: MODEL_CREATION_CONTENT.progressStepper.trainingArea, + icon: SquareShadowIcon, + path: MODELS_ROUTES.TRAINING_AREA, + }, + { + id: 4, + title: MODEL_CREATION_CONTENT.progressStepper.trainingSettings, + icon: SettingsIcon, + path: MODELS_ROUTES.TRAINING_SETTINGS, + }, + { + id: 5, + title: MODEL_CREATION_CONTENT.progressStepper.submitModel, + icon: CloudIcon, + path: MODELS_ROUTES.MODEL_SUMMARY, + }, + { + id: 6, + title: MODEL_CREATION_CONTENT.progressStepper.confirmation, + icon: StarIcon, + path: MODELS_ROUTES.CONFIRMATION, + }, + ]; -const ModelCreationLayout = () => { +export const ModelFormsLayout = () => { const { pathname } = useLocation(); const [currentPageIndex, setCurrentPageIndex] = useState(0); @@ -104,7 +104,6 @@ const ModelCreationLayout = () => { ); }; -export default ModelCreationLayout; const ModelFormRouteValidator = ({ pathname, diff --git a/frontend/src/components/layouts/root-layout.tsx b/frontend/src/layouts/root-layout.tsx similarity index 80% rename from frontend/src/components/layouts/root-layout.tsx rename to frontend/src/layouts/root-layout.tsx index 397cd2e9..a55a736b 100644 --- a/frontend/src/components/layouts/root-layout.tsx +++ b/frontend/src/layouts/root-layout.tsx @@ -1,12 +1,12 @@ -import { NavBar } from "@/components/ui/navbar"; +import { NavBar } from "@/components/layout"; import { Outlet, useLocation } from "react-router-dom"; -import { Footer } from "@/components/ui/footer"; +import { Footer } from "@/components/layout"; import { useEffect } from "react"; import { Banner } from "@/components/ui/banner"; import { APPLICATION_ROUTES } from "@/utils"; import { useScrollToTop } from "@/hooks/use-scroll-to-element"; -const RootLayout = () => { +export const RootLayout = () => { const { pathname } = useLocation(); const { scrollToTop } = useScrollToTop(); // Scroll to top on pages switch. @@ -25,9 +25,9 @@ const RootLayout = () => { >
-
+ {!pathname.includes(APPLICATION_ROUTES.START_MAPPING_BASE) &&
}
); }; -export default RootLayout; + diff --git a/frontend/src/lib/geojsonToOsmPolygons.ts b/frontend/src/lib/geojson-to-osm.ts similarity index 100% rename from frontend/src/lib/geojsonToOsmPolygons.ts rename to frontend/src/lib/geojson-to-osm.ts diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts new file mode 100644 index 00000000..d3da8363 --- /dev/null +++ b/frontend/src/lib/index.ts @@ -0,0 +1 @@ +export * from './geojson-to-osm' \ No newline at end of file diff --git a/frontend/src/services/api-routes.ts b/frontend/src/services/api-routes.ts index 14cfc8c2..3b3cc20a 100644 --- a/frontend/src/services/api-routes.ts +++ b/frontend/src/services/api-routes.ts @@ -3,11 +3,13 @@ */ export const API_ENDPOINTS = { // Auth + LOGIN: "auth/login/", AUTH_CALLBACK: "auth/callback/", USER: "auth/me/", // OSM Database + GET_OSM_DATABASE_LAST_UPDATED: "https://api-prod.raw-data.hotosm.org/v1/status/", @@ -23,6 +25,7 @@ export const API_ENDPOINTS = { DELETE_APPROVED_PREDICTION: (id: number) => `approved-prediction/${id}/`, // KPIs + GET_KPI_STATS: "kpi/stats/ ", // GeoJSON to OSM @@ -34,36 +37,39 @@ export const API_ENDPOINTS = { GET_BANNER: "banner", // Models + GET_MODELS: "model/", CREATE_MODEL: "model/", UPDATE_MODEL: (modelId: string) => `model/${modelId}/`, GET_MODEL_DETAILS: (id: string) => `model/${id}`, GET_MODELS_CENTROIDS: "models/centroid", - // Trainings + // Training Areas & Datasets GET_TRAINING_DETAILS: (id: number) => `training/${id}`, GET_TRAINING_DATASETS: (searchQuery: string, ordering: string) => `dataset/?search=${searchQuery}&ordering=${ordering}`, GET_TRAINING_DATASET: (id: number) => `dataset/${id}`, - CREATE_TRAINING_DATASET: "dataset/", GET_TRAINING_AREA_GPX: (aoiId: number) => `aoi/gpx/${aoiId}`, - CREATE_TRAINING_AREA: "aoi/", GET_TRAINING_AREA_LABELS_FROM_OSM: (aoiId: number) => `label/osm/fetch/${aoiId}/`, - CREATE_TRAINING_AREA_LABELS: `label/`, GET_TRAINING_AREA_LABELS: (aoiId: number) => `label/?aoi=${aoiId}`, GET_TRAINING_DATASET_LABELS: (aoiDatasetId: number, bbox: string) => `label/?aoi__dataset=${aoiDatasetId}&in__bbox=${bbox}/`, - CREATE_TRAINING_REQUEST: "training/", - DELETE_TRAINING_AREA: (id: number) => `aoi/${id}/`, GET_TRAINING_AREAS: (datasetId: number, offset: number, limit: number) => `aoi/?dataset=${datasetId}&offset=${offset}&limit=${limit}`, + GET_TRAINING_AREA: (aoiId: number) => `aoi/${aoiId}/`, GET_TRAINING_STATUS: (taskId: string) => `training/status/${taskId}`, - UPDATE_TRAINING: (id: number) => `training/publish/${id}/`, GET_MODEL_TRAINING_HISTORY: (id: string) => `training/?model=${id}`, GET_TRAINING_FEEDBACKS: (trainingId: number) => `feedback/?training=${trainingId}`, + CREATE_TRAINING_AREA_LABELS: `label/`, + CREATE_TRAINING_DATASET: "dataset/", + CREATE_TRAINING_AREA: "aoi/", + CREATE_TRAINING_REQUEST: "training/", + UPDATE_TRAINING: (id: number) => `training/publish/${id}/`, + DELETE_TRAINING_AREA: (id: number) => `aoi/${id}/`, + // Workspace diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index f465f62e..cbcf8bf5 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -174,26 +174,47 @@ sl-alert.success::part(base) { /* General scrollbar styles */ ::-webkit-scrollbar { - width: var(--scrollbar-width); + width: var(--scrollbar-width); height: 5px; + opacity: 0; + transition: opacity 0.2s ease; +} + +::-webkit-scrollbar:hover { + opacity: 1; } /* Scrollbar track */ ::-webkit-scrollbar-track { - background: var(--scrollbar-track-bg); + background: var(--scrollbar-track-bg, #f1f1f1); } /* Scrollbar thumb */ ::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb-bg); + background: var(--scrollbar-thumb-bg, #888); border-radius: 5px; } /* Scrollbar thumb on hover */ ::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover-bg); + background: var(--scrollbar-thumb-hover-bg, #555); +} + +/* Hide scrollbar by default for specific elements */ +.scrollable::-webkit-scrollbar { + width: var(--scrollbar-width); + display: none; } +/* Show scrollbar when hovering over the scrollable container */ +.scrollable:hover::-webkit-scrollbar { + display: block; + transition: opacity 1.3s ease, display 1.3s ease; +} + +/* General scrollbar ends */ + + /* Popup customization starts */ .maplibregl-popup-content { @apply !rounded-[12px] !flex !flex-col !gap-y-4 !p-0; diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 5f40ae1b..5f4ba4d3 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -43,6 +43,7 @@ export const APPLICATION_ROUTES = { EDIT_MODEL_SUMMARY: `${MODELS_ROUTES.EDIT_MODEL_BASE}/${MODELS_ROUTES.MODEL_SUMMARY}`, // Model routes end + TRAINING_DATASETS: "/training-datasets", START_MAPPING_BASE: "/start-mapping/", START_MAPPING: "/start-mapping/:modelId", @@ -250,3 +251,16 @@ export const PREDICTION_API_FILE_EXTENSIONS = { * The remote url to JOSM. */ export const JOSM_REMOTE_URL = ENVS.JOSM_REMOTE_URL || "http://127.0.0.1:8111/"; + +/** + * The time to poll the backend for the status of the AOI training labels fetching, in milliseconds (ms). + * Defaults to 5000 ms (5 seconds). + */ +export const TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS = ENVS.TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS || 5000 + +/** +* The time to poll the backend for the status of the OSM last updated time, in milliseconds (ms). +* Data type: Positive Integer (e.g., 900). +* Default value: 10000 milliseconds (10 seconds). +*/ +export const OSM_LAST_UPDATED_POOLING_INTERVAL_MS = ENVS.OSM_LAST_UPDATED_POOLING_INTERVAL_MS || 10000 \ No newline at end of file diff --git a/frontend/src/utils/general-utils.ts b/frontend/src/utils/general-utils.ts index 0115596b..5beedd67 100644 --- a/frontend/src/utils/general-utils.ts +++ b/frontend/src/utils/general-utils.ts @@ -10,7 +10,7 @@ import { } from "@/utils"; import { useToastNotification } from "@/hooks/use-toast-notification"; import { TOAST_NOTIFICATIONS } from "@/constants"; -import { geojsonToOsmPolygons } from "@/lib/geojsonToOsmPolygons"; +import { geojsonToOsmPolygons } from "@/lib"; import bbox from "@turf/bbox"; /** From 55962e00c64444e2f4db065bd5b22f77fcb94cf9 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Mon, 23 Dec 2024 17:05:53 +0100 Subject: [PATCH 03/33] wip: fixed bg image bug --- .../src/components/shared/static-page-header/header.module.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/shared/static-page-header/header.module.css b/frontend/src/components/shared/static-page-header/header.module.css index 35efdb7d..a3917d99 100644 --- a/frontend/src/components/shared/static-page-header/header.module.css +++ b/frontend/src/components/shared/static-page-header/header.module.css @@ -1,5 +1,5 @@ .headerBackground { - background-image: url("../../../src/assets/svgs/header_bg_contour.svg"); + background-image: url("../../../assets/svgs/header_bg_contour.svg"); background-size: cover; background-position: center; background-repeat: no-repeat; From e458baa73c0733cd97203036949b479bae65d052 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Mon, 23 Dec 2024 21:13:27 +0100 Subject: [PATCH 04/33] wip: start mapping mobile v2 --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 501 ++++++++++++++++++ frontend/src/app/routes/account/models.tsx | 2 +- frontend/src/app/routes/landing.tsx | 11 +- .../src/app/routes/models/model-details.tsx | 2 +- .../src/app/routes/models/models-list.tsx | 5 +- frontend/src/app/routes/start-mapping.tsx | 39 +- frontend/src/assets/images/fAIr_logo.png | Bin 0 -> 1550 bytes frontend/src/assets/images/index.ts | 3 +- frontend/src/assets/svgs/hot_fair_logo.svg | 10 - frontend/src/assets/svgs/index.ts | 3 +- frontend/src/components/errors/fallback.tsx | 1 - .../components/errors/under-construction.tsx | 1 - .../landing/about-fair/about-fair.module.css | 2 +- .../landing/about-fair/about-fair.tsx | 3 - .../core-values/core-values.module.css | 2 +- .../landing/core-values/core-values.tsx | 1 - .../src/components/landing/cta/cta.module.css | 2 +- frontend/src/components/landing/cta/cta.tsx | 1 - .../src/components/landing/header/header.tsx | 1 - frontend/src/components/landing/index.ts | 2 +- frontend/src/components/landing/kpi/kpi.tsx | 1 - .../landing/tagline/tagline.module.css | 2 +- .../components/landing/tagline/tagline.tsx | 1 - .../src/components/layout/footer/footer.tsx | 1 - frontend/src/components/layout/index.ts | 4 +- .../src/components/layout/navbar/nav-logo.tsx | 17 +- .../src/components/layout/navbar/navbar.tsx | 2 - .../components/layout/navbar/user-profile.tsx | 14 +- .../map/controls/fit-to-bounds-control.tsx | 6 +- frontend/src/components/map/map.tsx | 15 +- .../components/map/setups/setup-maplibre.ts | 14 +- .../fair-process/fair-process.module.css | 2 +- .../shared/fair-process/fair-process.tsx | 2 - .../components/shared/faqs/faqs.module.css | 3 +- frontend/src/components/shared/index.ts | 4 +- frontend/src/components/shared/pagination.tsx | 4 +- frontend/src/components/ui/drawer/index.ts | 1 + .../components/ui/drawer/mobile-drawer.tsx | 46 ++ .../src/components/ui/dropdown/dropdown.tsx | 2 +- .../components/ui/form/input/input.module.css | 2 +- .../src/components/ui/form/select/select.tsx | 6 +- .../components/ui/icons/arrow-move-icon.tsx | 16 + frontend/src/components/ui/icons/index.ts | 3 +- .../ui/icons/no-training-area-icon.tsx | 62 +-- frontend/src/components/ui/navbar/navbar.tsx | 2 - .../src/components/ui/tooltip/tooltip.tsx | 3 +- frontend/src/config/env.ts | 9 +- .../ui-contents/start-mapping-content.ts | 10 +- frontend/src/enums/index.ts | 2 +- frontend/src/enums/training-area.ts | 6 +- .../features/model-creation/api/factory.ts | 1 - .../model-creation/api/get-trainings.ts | 7 +- .../components/model-summary.tsx | 8 +- .../components/progress-bar.tsx | 21 +- .../training-area/training-area-item.tsx | 277 ++++++---- .../training-area/training-area-list.tsx | 30 +- .../model-creation/hooks/use-polling.ts | 4 +- .../hooks/use-training-areas.ts | 9 +- frontend/src/features/models/api/factory.ts | 7 +- .../components/dialogs/model-files-dialog.tsx | 7 +- .../models/components/directory-tree.tsx | 3 +- .../components/filters/status-filter.tsx | 1 - .../components/maps/training-area-map.tsx | 8 +- .../features/models/components/model-card.tsx | 10 +- .../models/components/model-details-popup.tsx | 45 +- .../components/model-details-properties.tsx | 8 +- .../components/training-history-table.tsx | 223 ++++---- .../src/features/models/hooks/use-models.ts | 6 +- .../start-mapping/components/header.tsx | 160 +++--- .../start-mapping/components/index.ts | 4 +- .../components/mobile-drawer.tsx | 42 ++ .../start-mapping/components/model-action.tsx | 44 +- .../components/model-details-button.tsx | 17 + .../components/model-settings.tsx | 262 ++++----- frontend/src/hooks/use-dropdown-menu.ts | 11 +- frontend/src/hooks/use-map-instance.ts | 9 +- frontend/src/hooks/use-scroll-to-element.ts | 2 +- frontend/src/layouts/model-forms-layout.tsx | 75 ++- frontend/src/layouts/root-layout.tsx | 2 - frontend/src/lib/index.ts | 2 +- frontend/src/services/api-routes.ts | 1 - frontend/src/styles/index.css | 27 +- frontend/src/types/ui-contents.ts | 6 + frontend/src/utils/constants.ts | 16 +- frontend/src/utils/date-utils.ts | 1 + 86 files changed, 1479 insertions(+), 732 deletions(-) create mode 100644 frontend/src/assets/images/fAIr_logo.png delete mode 100644 frontend/src/assets/svgs/hot_fair_logo.svg create mode 100644 frontend/src/components/ui/drawer/mobile-drawer.tsx create mode 100644 frontend/src/components/ui/icons/arrow-move-icon.tsx create mode 100644 frontend/src/features/start-mapping/components/mobile-drawer.tsx create mode 100644 frontend/src/features/start-mapping/components/model-details-button.tsx diff --git a/frontend/package.json b/frontend/package.json index 277d3edc..ece9541c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "remark-gfm": "^4.0.0", "tailwind-merge": "^2.5.2", "terra-draw": "1.0.0-beta.8", + "vaul": "^1.1.2", "xmlbuilder2": "^3.1.1" }, "devDependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index a46a7f0a..c86ba863 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: terra-draw: specifier: 1.0.0-beta.8 version: 1.0.0-beta.8 + vaul: + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) xmlbuilder2: specifier: ^3.1.1 version: 3.1.1 @@ -820,6 +823,216 @@ packages: } engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + "@radix-ui/primitive@1.1.1": + resolution: + { + integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==, + } + + "@radix-ui/react-compose-refs@1.1.1": + resolution: + { + integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-context@1.1.1": + resolution: + { + integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-dialog@1.1.4": + resolution: + { + integrity: sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-dismissable-layer@1.1.3": + resolution: + { + integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-focus-guards@1.1.1": + resolution: + { + integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-focus-scope@1.1.1": + resolution: + { + integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-id@1.1.0": + resolution: + { + integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-portal@1.1.3": + resolution: + { + integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-presence@1.1.2": + resolution: + { + integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-primitive@2.0.1": + resolution: + { + integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + + "@radix-ui/react-slot@1.1.1": + resolution: + { + integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-callback-ref@1.1.0": + resolution: + { + integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-controllable-state@1.1.0": + resolution: + { + integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-escape-keydown@1.1.0": + resolution: + { + integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + "@radix-ui/react-use-layout-effect@1.1.0": + resolution: + { + integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@remix-run/router@1.19.2": resolution: { @@ -1430,6 +1643,13 @@ packages: integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, } + aria-hidden@1.2.4: + resolution: + { + integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==, + } + engines: { node: ">=10" } + arr-union@3.1.0: resolution: { @@ -1743,6 +1963,12 @@ packages: } engines: { node: ">=6" } + detect-node-es@1.1.0: + resolution: + { + integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, + } + devlop@1.1.0: resolution: { @@ -2140,6 +2366,13 @@ packages: } engines: { node: ">= 0.10" } + get-nonce@1.0.1: + resolution: + { + integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==, + } + engines: { node: ">=6" } + get-stream@6.0.1: resolution: { @@ -3471,6 +3704,32 @@ packages: } engines: { node: ">=0.10.0" } + react-remove-scroll-bar@2.3.8: + resolution: + { + integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + + react-remove-scroll@2.6.2: + resolution: + { + integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + react-router-dom@6.26.2: resolution: { @@ -3490,6 +3749,19 @@ packages: peerDependencies: react: ">=16.8" + react-style-singleton@2.2.3: + resolution: + { + integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + react@18.3.1: resolution: { @@ -4020,12 +4292,47 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, } + use-callback-ref@1.3.3: + resolution: + { + integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + + use-sidecar@1.1.3: + resolution: + { + integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + util-deprecate@1.0.2: resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, } + vaul@1.1.2: + resolution: + { + integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==, + } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vfile-message@4.0.2: resolution: { @@ -4527,6 +4834,141 @@ snapshots: "@pkgr/core@0.1.1": {} + "@radix-ui/primitive@1.1.1": {} + + "@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.10)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-context@1.1.1(@types/react@18.3.10)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-dialog@1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-context": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-dismissable-layer": 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-focus-guards": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-focus-scope": 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-id": 1.1.0(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-portal": 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-presence": 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-primitive": 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-slot": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-use-controllable-state": 1.1.0(@types/react@18.3.10)(react@18.3.1) + aria-hidden: 1.2.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-remove-scroll: 2.6.2(@types/react@18.3.10)(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + "@types/react-dom": 18.3.0 + + "@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/primitive": 1.1.1 + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-primitive": 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-use-escape-keydown": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + "@types/react-dom": 18.3.0 + + "@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.10)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-primitive": 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + "@types/react-dom": 18.3.0 + + "@radix-ui/react-id@1.1.0(@types/react@18.3.10)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-primitive": 2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + "@types/react-dom": 18.3.0 + + "@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.10)(react@18.3.1) + "@radix-ui/react-use-layout-effect": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + "@types/react-dom": 18.3.0 + + "@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)": + dependencies: + "@radix-ui/react-slot": 1.1.1(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + "@types/react-dom": 18.3.0 + + "@radix-ui/react-slot@1.1.1(@types/react@18.3.10)(react@18.3.1)": + dependencies: + "@radix-ui/react-compose-refs": 1.1.1(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.10)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.10)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.10)(react@18.3.1)": + dependencies: + "@radix-ui/react-use-callback-ref": 1.1.0(@types/react@18.3.10)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + + "@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.10)(react@18.3.1)": + dependencies: + react: 18.3.1 + optionalDependencies: + "@types/react": 18.3.10 + "@remix-run/router@1.19.2": {} "@rollup/rollup-android-arm-eabi@4.23.0": @@ -4925,6 +5367,10 @@ snapshots: argparse@2.0.1: {} + aria-hidden@1.2.4: + dependencies: + tslib: 2.7.0 + arr-union@3.1.0: {} assign-symbols@1.0.0: {} @@ -5088,6 +5534,8 @@ snapshots: dequal@2.0.3: {} + detect-node-es@1.1.0: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -5327,6 +5775,8 @@ snapshots: geojson@0.5.0: {} + get-nonce@1.0.1: {} + get-stream@6.0.1: {} get-value@2.0.6: {} @@ -6297,6 +6747,25 @@ snapshots: react-refresh@0.14.2: {} + react-remove-scroll-bar@2.3.8(@types/react@18.3.10)(react@18.3.1): + dependencies: + react: 18.3.1 + react-style-singleton: 2.2.3(@types/react@18.3.10)(react@18.3.1) + tslib: 2.7.0 + optionalDependencies: + "@types/react": 18.3.10 + + react-remove-scroll@2.6.2(@types/react@18.3.10)(react@18.3.1): + dependencies: + react: 18.3.1 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.10)(react@18.3.1) + react-style-singleton: 2.2.3(@types/react@18.3.10)(react@18.3.1) + tslib: 2.7.0 + use-callback-ref: 1.3.3(@types/react@18.3.10)(react@18.3.1) + use-sidecar: 1.1.3(@types/react@18.3.10)(react@18.3.1) + optionalDependencies: + "@types/react": 18.3.10 + react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: "@remix-run/router": 1.19.2 @@ -6309,6 +6778,14 @@ snapshots: "@remix-run/router": 1.19.2 react: 18.3.1 + react-style-singleton@2.2.3(@types/react@18.3.10)(react@18.3.1): + dependencies: + get-nonce: 1.0.1 + react: 18.3.1 + tslib: 2.7.0 + optionalDependencies: + "@types/react": 18.3.10 + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -6665,8 +7142,32 @@ snapshots: dependencies: punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@18.3.10)(react@18.3.1): + dependencies: + react: 18.3.1 + tslib: 2.7.0 + optionalDependencies: + "@types/react": 18.3.10 + + use-sidecar@1.1.3(@types/react@18.3.10)(react@18.3.1): + dependencies: + detect-node-es: 1.1.0 + react: 18.3.1 + tslib: 2.7.0 + optionalDependencies: + "@types/react": 18.3.10 + util-deprecate@1.0.2: {} + vaul@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + "@radix-ui/react-dialog": 1.1.4(@types/react-dom@18.3.0)(@types/react@18.3.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - "@types/react" + - "@types/react-dom" + vfile-message@4.0.2: dependencies: "@types/unist": 3.0.3 diff --git a/frontend/src/app/routes/account/models.tsx b/frontend/src/app/routes/account/models.tsx index 8206295b..3988ce38 100644 --- a/frontend/src/app/routes/account/models.tsx +++ b/frontend/src/app/routes/account/models.tsx @@ -1,4 +1,4 @@ -import { Pagination, } from "@/components/shared"; +import { Pagination } from "@/components/shared"; import { Head } from "@/components/seo"; import { LayoutView } from "@/enums"; import { LayoutToggle, PageHeader } from "@/features/models/components"; diff --git a/frontend/src/app/routes/landing.tsx b/frontend/src/app/routes/landing.tsx index 134671b1..2cb5278c 100644 --- a/frontend/src/app/routes/landing.tsx +++ b/frontend/src/app/routes/landing.tsx @@ -1,5 +1,14 @@ import { FAQs } from "@/components/shared"; -import { Header, Kpi, TaglineBanner, TheFAIRProcess, CallToAction, Corevalues, CoreFeatures, WhatIsFAIR } from "@/components/landing"; +import { + Header, + Kpi, + TaglineBanner, + TheFAIRProcess, + CallToAction, + Corevalues, + CoreFeatures, + WhatIsFAIR, +} from "@/components/landing"; import { Head } from "@/components/seo"; diff --git a/frontend/src/app/routes/models/model-details.tsx b/frontend/src/app/routes/models/model-details.tsx index c40cf5d7..2eac802b 100644 --- a/frontend/src/app/routes/models/model-details.tsx +++ b/frontend/src/app/routes/models/model-details.tsx @@ -166,4 +166,4 @@ export const ModelDetailsPage = () => {
); -}; \ No newline at end of file +}; diff --git a/frontend/src/app/routes/models/models-list.tsx b/frontend/src/app/routes/models/models-list.tsx index 0e932e43..49529a02 100644 --- a/frontend/src/app/routes/models/models-list.tsx +++ b/frontend/src/app/routes/models/models-list.tsx @@ -91,10 +91,7 @@ export const ModelsPage = () => { if (mapViewIsActive) { return ( -
+
{ ] as BBOX, }; + const { isMobile } = useScreenSize(); + + const disablePrediction = + currentZoom < MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION; + + const popupAnchorId = "model-details"; + + const [showModelDetails, setShowModelDetails] = useState(false); + return ( <> + setShowModelDetails(false)} + anchor={popupAnchorId} + model={data} + trainingDataset={trainingDataset} + trainingDatasetIsPending={trainingDatasetIsPending} + trainingDatasetIsError={trainingDatasetIsError} + />
-
+ +
{ trainingConfig={trainingConfig} setModelPredictions={setModelPredictions} map={map} - currentZoom={currentZoom} + disablePrediction={disablePrediction} + popupAnchorId={popupAnchorId} + showModelDetails={showModelDetails} + setShowModelDetails={setShowModelDetails} />
diff --git a/frontend/src/assets/images/fAIr_logo.png b/frontend/src/assets/images/fAIr_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a2377e4f3627b144cccba51319828cf039fd4aaa GIT binary patch literal 1550 zcmV+p2J!icP)|1-M_>mrC!Dx#%o_eORC7U_B=R0TS zf9CtX^PL4uOiWBnOicbi2(b+Am6a`ER;z?0!GM7xulIzYG!w@f)4*=`ha-{I)GS4k zXmz^_;PZWp*C6H_;rN}-X+cr^jh3tkNkSBaFABZhgGdlVLO9K3Wd+P)xkLl$KUvxN- zE=jN#6u>@b4usTHuwW3O&Q1hw-^Nfw1EK>1dbenYAS@~Ndi{t4RUyr8_t$jUqBV$% z5XK}5Yi1@UEnkkTRjVMTrD+X?90;KQ>{9w2!CzVe;-al6q^w7(yY`SX#vVuj{22!ilsizLO| zEEU)CNRo9L@TsijYO?$uO}Js~&u-EQ||Rf zo80bHQgKMtN*J%tGHDX*B_+^89v;TCOP4U`^l5}UI`Fu~`M#$CdteZrp#+|@Man>`!4kQjO^-rFwMEa(a0P!;+OU20%}v3rF~?!#TF=jhRw zZw{k~6DAljo17|&=bINT${IKR3wikaLUJCFz?y~g%F z##k{%Mt*Hvwruh%_gAwW35P4_dPiqIWkX{T?es2?FiyztN1&+*R&p;fB}LVijf^1J zs-5M>4`2&ygM-J|&YdrwUp4*Cy?(vV#@I(%X_v!Gjluf*f1g@_b;=Ya<#89WWMn9B z=#4dN(6M0ydXFDR*4nky`OobX+n3yK_~uQ;HC3o)vYe~&+1TFDU>h~mb>g=2@^@uf zzDQ|#hFU*Qy?OihL$Jm?pXt)MX%mKS+)(YKio1%^?y*>N=x>M~&s{1}Xekj^>kr(y zqZELz10Sy?fI$Ik;5O$K7U2zRT0<^naZ$$)w|JyMO$w4N!`@FcW?gyg-5)#6> z?{ejnm@DMvZz4GaXwzIt_1{G!#t94IG)we%31dIgBpFBov`BpvW3Iy5+Ll - - - - - - - - - diff --git a/frontend/src/assets/svgs/index.ts b/frontend/src/assets/svgs/index.ts index 19c871e2..258cbffc 100644 --- a/frontend/src/assets/svgs/index.ts +++ b/frontend/src/assets/svgs/index.ts @@ -2,6 +2,5 @@ export { default as DashedLineConnector } from "@/assets/svgs/dashed_line.svg"; export { default as AIIcon } from "@/assets/svgs/fair_ai_icon.svg"; export { default as fAIrValues } from "@/assets/svgs/fair_values.svg"; export { default as HamburgerIcon } from "@/assets/svgs/hamburger_icon.svg"; -export { default as HOTFairLogo } from "@/assets/svgs/hot_fair_logo.svg"; export { default as JOSMLogo } from "@/assets/svgs/josm_logo.svg"; -export { default as OSMLogo } from "@/assets/svgs/osm_logo.svg"; \ No newline at end of file +export { default as OSMLogo } from "@/assets/svgs/osm_logo.svg"; diff --git a/frontend/src/components/errors/fallback.tsx b/frontend/src/components/errors/fallback.tsx index 47d20eae..b4a327ee 100644 --- a/frontend/src/components/errors/fallback.tsx +++ b/frontend/src/components/errors/fallback.tsx @@ -22,4 +22,3 @@ export const MainErrorFallback = () => { ); }; - diff --git a/frontend/src/components/errors/under-construction.tsx b/frontend/src/components/errors/under-construction.tsx index fc99ea2e..bc0a578e 100644 --- a/frontend/src/components/errors/under-construction.tsx +++ b/frontend/src/components/errors/under-construction.tsx @@ -20,4 +20,3 @@ export const PageUnderConstruction = () => {
); }; - diff --git a/frontend/src/components/landing/about-fair/about-fair.module.css b/frontend/src/components/landing/about-fair/about-fair.module.css index 4613ac9b..8a2832c5 100644 --- a/frontend/src/components/landing/about-fair/about-fair.module.css +++ b/frontend/src/components/landing/about-fair/about-fair.module.css @@ -89,4 +89,4 @@ min-width: 366px; min-height: 366px; } -} \ No newline at end of file +} diff --git a/frontend/src/components/landing/about-fair/about-fair.tsx b/frontend/src/components/landing/about-fair/about-fair.tsx index bc475696..659730c3 100644 --- a/frontend/src/components/landing/about-fair/about-fair.tsx +++ b/frontend/src/components/landing/about-fair/about-fair.tsx @@ -1,4 +1,3 @@ - import styles from "./about-fair.module.css"; import { APP_CONTENT } from "@/utils/content"; import { Image } from "@/components/ui/image"; @@ -18,5 +17,3 @@ export const WhatIsFAIR = () => {
); }; - - diff --git a/frontend/src/components/landing/core-values/core-values.module.css b/frontend/src/components/landing/core-values/core-values.module.css index a3ffc6e3..b149ebbb 100644 --- a/frontend/src/components/landing/core-values/core-values.module.css +++ b/frontend/src/components/landing/core-values/core-values.module.css @@ -240,4 +240,4 @@ .dashedLineWrapper { top: 320px; } -} \ No newline at end of file +} diff --git a/frontend/src/components/landing/core-values/core-values.tsx b/frontend/src/components/landing/core-values/core-values.tsx index d6068b99..81c3ab39 100644 --- a/frontend/src/components/landing/core-values/core-values.tsx +++ b/frontend/src/components/landing/core-values/core-values.tsx @@ -108,4 +108,3 @@ export const Corevalues = () => {
); }; - diff --git a/frontend/src/components/landing/cta/cta.module.css b/frontend/src/components/landing/cta/cta.module.css index bdcd0e40..5f198eca 100644 --- a/frontend/src/components/landing/cta/cta.module.css +++ b/frontend/src/components/landing/cta/cta.module.css @@ -159,4 +159,4 @@ right: 0; left: -54px; } -} \ No newline at end of file +} diff --git a/frontend/src/components/landing/cta/cta.tsx b/frontend/src/components/landing/cta/cta.tsx index 919ec53d..e0c936f3 100644 --- a/frontend/src/components/landing/cta/cta.tsx +++ b/frontend/src/components/landing/cta/cta.tsx @@ -39,4 +39,3 @@ export const CallToAction = () => {
); }; - diff --git a/frontend/src/components/landing/header/header.tsx b/frontend/src/components/landing/header/header.tsx index 21a72203..29e1bf5d 100644 --- a/frontend/src/components/landing/header/header.tsx +++ b/frontend/src/components/landing/header/header.tsx @@ -48,4 +48,3 @@ export const Header = () => { ); }; - diff --git a/frontend/src/components/landing/index.ts b/frontend/src/components/landing/index.ts index 8ebaf391..86e44dc5 100644 --- a/frontend/src/components/landing/index.ts +++ b/frontend/src/components/landing/index.ts @@ -5,4 +5,4 @@ export { CoreFeatures } from "./core-features/core-features"; export { Corevalues } from "./core-values/core-values"; export { TaglineBanner } from "./tagline/tagline"; export { CallToAction } from "./cta/cta"; -export * from './header' \ No newline at end of file +export * from "./header"; diff --git a/frontend/src/components/landing/kpi/kpi.tsx b/frontend/src/components/landing/kpi/kpi.tsx index 8cedf868..515d32ef 100644 --- a/frontend/src/components/landing/kpi/kpi.tsx +++ b/frontend/src/components/landing/kpi/kpi.tsx @@ -70,4 +70,3 @@ export const Kpi = () => {
); }; - diff --git a/frontend/src/components/landing/tagline/tagline.module.css b/frontend/src/components/landing/tagline/tagline.module.css index 9bcb64d5..4927b66f 100644 --- a/frontend/src/components/landing/tagline/tagline.module.css +++ b/frontend/src/components/landing/tagline/tagline.module.css @@ -37,4 +37,4 @@ .taglineBanner { padding: 0 var(--hot-fair-spacing-extra-large); } -} \ No newline at end of file +} diff --git a/frontend/src/components/landing/tagline/tagline.tsx b/frontend/src/components/landing/tagline/tagline.tsx index f16505a4..041e90bb 100644 --- a/frontend/src/components/landing/tagline/tagline.tsx +++ b/frontend/src/components/landing/tagline/tagline.tsx @@ -16,4 +16,3 @@ export const TaglineBanner = () => {
); }; - diff --git a/frontend/src/components/layout/footer/footer.tsx b/frontend/src/components/layout/footer/footer.tsx index 1539edb9..25c9b3ca 100644 --- a/frontend/src/components/layout/footer/footer.tsx +++ b/frontend/src/components/layout/footer/footer.tsx @@ -123,4 +123,3 @@ export const Footer = () => { ); }; - diff --git a/frontend/src/components/layout/index.ts b/frontend/src/components/layout/index.ts index 05494aaa..b3208605 100644 --- a/frontend/src/components/layout/index.ts +++ b/frontend/src/components/layout/index.ts @@ -1,2 +1,2 @@ -export * from './footer' -export * from './navbar' \ No newline at end of file +export * from "./footer"; +export * from "./navbar"; diff --git a/frontend/src/components/layout/navbar/nav-logo.tsx b/frontend/src/components/layout/navbar/nav-logo.tsx index 789c5cc7..e7bd3a22 100644 --- a/frontend/src/components/layout/navbar/nav-logo.tsx +++ b/frontend/src/components/layout/navbar/nav-logo.tsx @@ -1,12 +1,12 @@ import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; import { Image } from "@/components/ui/image"; import { useNavigate } from "react-router-dom"; -import { HOTFairLogo } from "@/assets/svgs"; +import { fAIrLogo } from "@/assets/images"; export const NavLogo = ({ onClick, - width = "125px", - height = "72px", + width = "60px", + height = "22px", }: { onClick?: () => void; width?: string; @@ -17,15 +17,18 @@ export const NavLogo = ({ onClick ? onClick() : navigate(APPLICATION_ROUTES.HOMEPAGE); }; return ( - ); }; - - diff --git a/frontend/src/components/layout/navbar/navbar.tsx b/frontend/src/components/layout/navbar/navbar.tsx index a970fba9..c9b40e11 100644 --- a/frontend/src/components/layout/navbar/navbar.tsx +++ b/frontend/src/components/layout/navbar/navbar.tsx @@ -84,8 +84,6 @@ export const NavBar = () => { ); }; - - type TNavBarLinks = { title: string; href: string; diff --git a/frontend/src/components/layout/navbar/user-profile.tsx b/frontend/src/components/layout/navbar/user-profile.tsx index b4317334..b42dd593 100644 --- a/frontend/src/components/layout/navbar/user-profile.tsx +++ b/frontend/src/components/layout/navbar/user-profile.tsx @@ -6,13 +6,19 @@ import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { useAuth } from "@/app/providers/auth-provider"; -export const UserProfile = ({ hideFullName }: { hideFullName?: boolean }) => { +export const UserProfile = ({ + hideFullName, + smallerSize, +}: { + hideFullName?: boolean; + smallerSize?: boolean; +}) => { const { user, logout } = useAuth(); const { onDropdownHide, onDropdownShow, dropdownIsOpened } = useDropdownMenu(); const navigate = useNavigate(); - + const size = smallerSize ? "35px" : "40px"; return ( { label={user.username} loading="lazy" initials={user.username.charAt(0)} + // @ts-expect-error bad type definition + style={{ "--size": size }} /> {!hideFullName && (

{user.username}

@@ -60,5 +68,3 @@ export const UserProfile = ({ hideFullName }: { hideFullName?: boolean }) => { >
); }; - - diff --git a/frontend/src/components/map/controls/fit-to-bounds-control.tsx b/frontend/src/components/map/controls/fit-to-bounds-control.tsx index 7b254146..e528f9dc 100644 --- a/frontend/src/components/map/controls/fit-to-bounds-control.tsx +++ b/frontend/src/components/map/controls/fit-to-bounds-control.tsx @@ -1,15 +1,15 @@ import { ToolTip } from "@/components/ui/tooltip"; -import { FullScreenIcon } from "@/components/ui/icons"; +import { ArrowMoveIcon } from "@/components/ui/icons"; import { mapContents } from "@/constants"; export const FitToBounds = ({ onClick }: { onClick: () => void }) => { return ( ); diff --git a/frontend/src/components/map/map.tsx b/frontend/src/components/map/map.tsx index 09c3250f..abe2f1b1 100644 --- a/frontend/src/components/map/map.tsx +++ b/frontend/src/components/map/map.tsx @@ -105,6 +105,13 @@ export const MapComponent: React.FC = ({ {currentZoom ? ( ) : null} + {fitToBounds ? ( + + map?.fitBounds(bounds as LngLatBoundsLike, { padding: 10 }) + } + /> + ) : null} {geolocationControl && } {drawControl && terraDraw && drawingMode && setDrawingMode && ( = ({ layerControlData, currentZoom, drawingMode && setDrawingMode, + fitToBounds, ]); return ( @@ -153,13 +161,6 @@ export const MapComponent: React.FC = ({ )} {showTileBoundary && } - {fitToBounds && map && ( - - map?.fitBounds(bounds as LngLatBoundsLike, { padding: 10 }) - } - /> - )} {children}
); diff --git a/frontend/src/components/map/setups/setup-maplibre.ts b/frontend/src/components/map/setups/setup-maplibre.ts index 66367518..bfaffbe9 100644 --- a/frontend/src/components/map/setups/setup-maplibre.ts +++ b/frontend/src/components/map/setups/setup-maplibre.ts @@ -5,7 +5,7 @@ import { Protocol } from "pmtiles"; export const setupMaplibreMap = ( containerRef: React.RefObject, style: StyleSpecification | string, - pmtiles: boolean + pmtiles: boolean, ): Map => { // Check if RTL plugin is needed and set it if (maplibregl.getRTLTextPluginStatus() === "unavailable") { @@ -27,21 +27,21 @@ export const setupMaplibreMap = ( zoom: 0.5, minZoom: 1, maxZoom: MAX_ZOOM_LEVEL, - pitchWithRotate: false - }) + pitchWithRotate: false, + }); // Prevent the map from rotating - map.on('rotatestart', () => { + map.on("rotatestart", () => { map.setBearing(0); }); - map.on('rotate', () => { + map.on("rotate", () => { map.setBearing(0); }); - map.on('rotateend', () => { + map.on("rotateend", () => { map.setBearing(0); }); - return map + return map; }; diff --git a/frontend/src/components/shared/fair-process/fair-process.module.css b/frontend/src/components/shared/fair-process/fair-process.module.css index 43376dc9..427cfb98 100644 --- a/frontend/src/components/shared/fair-process/fair-process.module.css +++ b/frontend/src/components/shared/fair-process/fair-process.module.css @@ -8,4 +8,4 @@ .fairProcess { padding: 100px var(--hot-fair-spacing-extra-large); } -} \ No newline at end of file +} diff --git a/frontend/src/components/shared/fair-process/fair-process.tsx b/frontend/src/components/shared/fair-process/fair-process.tsx index 61bacfd3..5de4188c 100644 --- a/frontend/src/components/shared/fair-process/fair-process.tsx +++ b/frontend/src/components/shared/fair-process/fair-process.tsx @@ -156,5 +156,3 @@ export const TheFAIRProcess = ({ ); }; - - diff --git a/frontend/src/components/shared/faqs/faqs.module.css b/frontend/src/components/shared/faqs/faqs.module.css index 227665a6..5ca5eb40 100644 --- a/frontend/src/components/shared/faqs/faqs.module.css +++ b/frontend/src/components/shared/faqs/faqs.module.css @@ -27,7 +27,6 @@ /* md: */ @media (min-width: 768px) { - .heading { max-width: 218px; font-size: var(--hot-fair-font-size-title-2); @@ -38,4 +37,4 @@ margin: 0 auto; max-width: 844px; } -} \ No newline at end of file +} diff --git a/frontend/src/components/shared/index.ts b/frontend/src/components/shared/index.ts index b981988b..745734d5 100644 --- a/frontend/src/components/shared/index.ts +++ b/frontend/src/components/shared/index.ts @@ -1,5 +1,5 @@ export { Header } from "./static-page-header/header"; export { FAQs } from "./faqs/faqs"; export { SectionHeader } from "./section-header"; -export * from './pagination' -export { TheFAIRProcess } from './fair-process/fair-process' \ No newline at end of file +export * from "./pagination"; +export { TheFAIRProcess } from "./fair-process/fair-process"; diff --git a/frontend/src/components/shared/pagination.tsx b/frontend/src/components/shared/pagination.tsx index ab45dad2..fc7c5ec6 100644 --- a/frontend/src/components/shared/pagination.tsx +++ b/frontend/src/components/shared/pagination.tsx @@ -64,7 +64,7 @@ export const Pagination: React.FC = ({ return (

= ({

); }; - - diff --git a/frontend/src/components/ui/drawer/index.ts b/frontend/src/components/ui/drawer/index.ts index d76109c7..9040e68b 100644 --- a/frontend/src/components/ui/drawer/index.ts +++ b/frontend/src/components/ui/drawer/index.ts @@ -1 +1,2 @@ export { default as Drawer } from "./drawer"; +export { MobileDrawer } from "./mobile-drawer"; diff --git a/frontend/src/components/ui/drawer/mobile-drawer.tsx b/frontend/src/components/ui/drawer/mobile-drawer.tsx new file mode 100644 index 00000000..f4b5a00d --- /dev/null +++ b/frontend/src/components/ui/drawer/mobile-drawer.tsx @@ -0,0 +1,46 @@ +import { cn } from "@/utils"; +import React, { useState } from "react"; +import { Drawer } from "vaul"; + +const snapPoints = ["150px", "355px", 1]; + +export const MobileDrawer = ({ + open, + children, +}: { + open: boolean; + children: React.ReactNode; +}) => { + const [snap, setSnap] = useState(snapPoints[0]); + + return ( + + + + +
+
+ + {children} +
+ + + + ); +}; diff --git a/frontend/src/components/ui/dropdown/dropdown.tsx b/frontend/src/components/ui/dropdown/dropdown.tsx index a4edd895..df635e97 100644 --- a/frontend/src/components/ui/dropdown/dropdown.tsx +++ b/frontend/src/components/ui/dropdown/dropdown.tsx @@ -121,7 +121,7 @@ const DropDown: React.FC = ({ /> )}
-
+
{menuItems && menuItems.length > 0 ? ( {menuItems?.map((menuItem, id) => ( diff --git a/frontend/src/components/ui/form/input/input.module.css b/frontend/src/components/ui/form/input/input.module.css index 54eb9269..8a3fcd7e 100644 --- a/frontend/src/components/ui/form/input/input.module.css +++ b/frontend/src/components/ui/form/input/input.module.css @@ -26,5 +26,5 @@ sl-input.customInput::part(input)::placeholder { sl-input::part(input) { color: var(--hot-fair-color-dark); - font-size: var(--hot-fair-font-size-body-text-2base); + font-size: var(--hot-fair-font-size-body-text-3); } diff --git a/frontend/src/components/ui/form/select/select.tsx b/frontend/src/components/ui/form/select/select.tsx index 4e6fd985..19aad434 100644 --- a/frontend/src/components/ui/form/select/select.tsx +++ b/frontend/src/components/ui/form/select/select.tsx @@ -45,9 +45,9 @@ const Select: React.FC = ({ } value={String(defaultValue)} onSlChange={(e) => { - e.preventDefault(); - e.stopImmediatePropagation(); e.stopPropagation(); + e.stopImmediatePropagation(); + e.preventDefault(); //@ts-expect-error bad type definition handleChange(e); }} @@ -68,7 +68,7 @@ const Select: React.FC = ({ value={option.value as string} className="flex flex-col gap-y-1" > - {option.name} + {option.name} {option.suffix} ))} diff --git a/frontend/src/components/ui/icons/arrow-move-icon.tsx b/frontend/src/components/ui/icons/arrow-move-icon.tsx new file mode 100644 index 00000000..d614921a --- /dev/null +++ b/frontend/src/components/ui/icons/arrow-move-icon.tsx @@ -0,0 +1,16 @@ +import { IconProps } from "@/types"; +import React from "react"; + +export const ArrowMoveIcon: React.FC = (props) => ( + + + +); diff --git a/frontend/src/components/ui/icons/index.ts b/frontend/src/components/ui/icons/index.ts index e631e296..7d5eed6a 100644 --- a/frontend/src/components/ui/icons/index.ts +++ b/frontend/src/components/ui/icons/index.ts @@ -54,4 +54,5 @@ export { BookTemplateIcon } from "./book-template-icon"; export { YouTubePlayCircleIcon } from "./youtube-play-circle-icon"; export { CheckIcon } from "./check-icon"; export { DesktopFlowIcon } from "./desktop-flow-icon"; -export { NoTrainingAreaIcon } from './no-training-area-icon' \ No newline at end of file +export { NoTrainingAreaIcon } from "./no-training-area-icon"; +export { ArrowMoveIcon } from "./arrow-move-icon"; diff --git a/frontend/src/components/ui/icons/no-training-area-icon.tsx b/frontend/src/components/ui/icons/no-training-area-icon.tsx index 4fb52f63..2922961d 100644 --- a/frontend/src/components/ui/icons/no-training-area-icon.tsx +++ b/frontend/src/components/ui/icons/no-training-area-icon.tsx @@ -1,36 +1,30 @@ -import { IconProps } from "@/types" +import { IconProps } from "@/types"; export const NoTrainingAreaIcon: React.FC = (props) => ( - - - - - -) + + + + + +); diff --git a/frontend/src/components/ui/navbar/navbar.tsx b/frontend/src/components/ui/navbar/navbar.tsx index cadfbe7a..b5e5f483 100644 --- a/frontend/src/components/ui/navbar/navbar.tsx +++ b/frontend/src/components/ui/navbar/navbar.tsx @@ -84,8 +84,6 @@ export const NavBar = () => { ); }; - - type TNavBarLinks = { title: string; href: string; diff --git a/frontend/src/components/ui/tooltip/tooltip.tsx b/frontend/src/components/ui/tooltip/tooltip.tsx index 44fee00b..3b781b0a 100644 --- a/frontend/src/components/ui/tooltip/tooltip.tsx +++ b/frontend/src/components/ui/tooltip/tooltip.tsx @@ -5,7 +5,7 @@ import { InfoIcon } from "@/components/ui/icons"; import { ToolTipPlacement } from "@/enums"; type ToolTipProps = { - content?: string | React.ReactElement; + content?: string | React.ReactElement | null; children?: React.ReactNode; placement?: | ToolTipPlacement.RIGHT @@ -23,6 +23,7 @@ const ToolTip: React.FC = ({ e.stopImmediatePropagation(); e.stopPropagation(); }; + if (!content) return children; return ( stopPropagations(e)} diff --git a/frontend/src/config/env.ts b/frontend/src/config/env.ts index 76bcbe96..acb8ef2c 100644 --- a/frontend/src/config/env.ts +++ b/frontend/src/config/env.ts @@ -2,7 +2,6 @@ * The environment variables. Ideally these values should be set in the .env file. */ export const ENVS = { - /** # The backend api endpoint url. # Data type: String (e.g., http://localhost:8000/api/v1/). @@ -199,7 +198,8 @@ export const ENVS = { # Data type: Positive Integer (e.g., 900). # Default value: 5000 milliseconds (5 seconds). */ - TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS: import.meta.env.VITE_TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS, + TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS: import.meta.env + .VITE_TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS, /** # The time to poll the backend for the status of the OSM last updated time, in milliseconds (ms). @@ -207,7 +207,6 @@ export const ENVS = { # Default value: 10000 milliseconds (10 seconds). */ - OSM_LAST_UPDATED_POOLING_INTERVAL_MS: import.meta.env.VITE_OSM_LAST_UPDATED_POOLING_INTERVAL_MS + OSM_LAST_UPDATED_POOLING_INTERVAL_MS: import.meta.env + .VITE_OSM_LAST_UPDATED_POOLING_INTERVAL_MS, }; - - diff --git a/frontend/src/constants/ui-contents/start-mapping-content.ts b/frontend/src/constants/ui-contents/start-mapping-content.ts index 9a1572ee..adb46430 100644 --- a/frontend/src/constants/ui-contents/start-mapping-content.ts +++ b/frontend/src/constants/ui-contents/start-mapping-content.ts @@ -37,6 +37,7 @@ export const startMappingPageContent: TStartMappingPageContent = { }, buttons: { runPrediction: "Run prediction", + tooltip: "Zoom in to run predictions", download: { label: "Actions", options: { @@ -46,7 +47,7 @@ export const startMappingPageContent: TStartMappingPageContent = { openAcceptedFeaturesInJOSM: "Open accepted Features to JOSM", }, }, - predictionInProgress: "Running prediction...", + predictionInProgress: "Running...", }, settings: { useJOSMQ: { @@ -65,15 +66,20 @@ export const startMappingPageContent: TStartMappingPageContent = { label: "Area", tooltip: "area", }, + tooltip: "Settings", }, mapData: { - title: "Map Data", + title: "Predictions", accepted: "Accepted", rejected: "Rejected", }, + actions: { + disabledModeTooltip: "Run prediction to see actions", + }, modelDetails: { error: "Error retrieving model information.", label: "Model Details", + tooltip: "Model Details", popover: { title: "Model Details", modelId: "Model ID", diff --git a/frontend/src/enums/index.ts b/frontend/src/enums/index.ts index 194f7a87..e768f623 100644 --- a/frontend/src/enums/index.ts +++ b/frontend/src/enums/index.ts @@ -1,3 +1,3 @@ export * from "./common"; export * from "./map"; -export * from './models' \ No newline at end of file +export * from "./models"; diff --git a/frontend/src/enums/training-area.ts b/frontend/src/enums/training-area.ts index 15613db4..423b0dc4 100644 --- a/frontend/src/enums/training-area.ts +++ b/frontend/src/enums/training-area.ts @@ -1,5 +1,5 @@ export enum LabelStatus { - DOWNLOADED = 1, - NOT_DOWNLOADED = -1, - DOWNLOADING = 0 + DOWNLOADED = 1, + NOT_DOWNLOADED = -1, + DOWNLOADING = 0, } diff --git a/frontend/src/features/model-creation/api/factory.ts b/frontend/src/features/model-creation/api/factory.ts index 5241dad2..7c545b18 100644 --- a/frontend/src/features/model-creation/api/factory.ts +++ b/frontend/src/features/model-creation/api/factory.ts @@ -62,7 +62,6 @@ export const getTrainingAreaLabelsQueryOptions = (aoiId: number) => { }); }; - export const getTrainingAreaQueryOptions = (aoiId: number) => { return queryOptions({ queryKey: ["training-area", aoiId], diff --git a/frontend/src/features/model-creation/api/get-trainings.ts b/frontend/src/features/model-creation/api/get-trainings.ts index c7b89141..09dfd4ef 100644 --- a/frontend/src/features/model-creation/api/get-trainings.ts +++ b/frontend/src/features/model-creation/api/get-trainings.ts @@ -2,7 +2,6 @@ import { API_ENDPOINTS, apiClient } from "@/services"; import { FeatureCollection, PaginatedTrainingArea, - TTrainingAreaFeature, TTrainingDataset, } from "@/types"; @@ -51,13 +50,9 @@ export const getTrainingAreaLabels = async ( return res.data; }; - - export const getTrainingArea = async ( aoiId: number, ): Promise => { - const res = await apiClient.get( - API_ENDPOINTS.GET_TRAINING_AREA(aoiId), - ); + const res = await apiClient.get(API_ENDPOINTS.GET_TRAINING_AREA(aoiId)); return res.data; }; diff --git a/frontend/src/features/model-creation/components/model-summary.tsx b/frontend/src/features/model-creation/components/model-summary.tsx index 670a1e9c..d8714b97 100644 --- a/frontend/src/features/model-creation/components/model-summary.tsx +++ b/frontend/src/features/model-creation/components/model-summary.tsx @@ -77,8 +77,12 @@ const ModelSummaryForm = () => { content: [ `${MODEL_CREATION_CONTENT.trainingSettings.form.epoch.label}: ${formData.epoch}`, `${MODEL_CREATION_CONTENT.trainingSettings.form.batchSize.label}: ${formData.batchSize}`, - formData.baseModel === BASE_MODELS.RAMP ? `${MODEL_CREATION_CONTENT.trainingSettings.form.contactSpacing.label}: ${formData.contactSpacing}` : '', - formData.baseModel === BASE_MODELS.RAMP ? `${MODEL_CREATION_CONTENT.trainingSettings.form.boundaryWidth.label}: ${formData.boundaryWidth}` : '', + formData.baseModel === BASE_MODELS.RAMP + ? `${MODEL_CREATION_CONTENT.trainingSettings.form.contactSpacing.label}: ${formData.contactSpacing}` + : "", + formData.baseModel === BASE_MODELS.RAMP + ? `${MODEL_CREATION_CONTENT.trainingSettings.form.boundaryWidth.label}: ${formData.boundaryWidth}` + : "", ], }, ]; diff --git a/frontend/src/features/model-creation/components/progress-bar.tsx b/frontend/src/features/model-creation/components/progress-bar.tsx index 1615f153..9d128fa3 100644 --- a/frontend/src/features/model-creation/components/progress-bar.tsx +++ b/frontend/src/features/model-creation/components/progress-bar.tsx @@ -22,16 +22,22 @@ const ProgressBar: React.FC = memo( const container = containerRef.current; const activeStep = activeStepRef.current; - const offset = activeStep.offsetLeft - (container.offsetWidth / 2) + (activeStep.offsetWidth / 2); + const offset = + activeStep.offsetLeft - + container.offsetWidth / 2 + + activeStep.offsetWidth / 2; container.scrollTo({ left: offset, - behavior: 'smooth', + behavior: "smooth", }); } }, [currentPath]); return ( -
+
{pages.map((step, index) => { const activeStep = currentPath.includes(step.path); const isLastPage = index === pages.length - 1; @@ -52,9 +58,10 @@ const ProgressBar: React.FC = memo( ) : ( @@ -71,4 +78,4 @@ const ProgressBar: React.FC = memo( }, ); -export default ProgressBar; \ No newline at end of file +export default ProgressBar; diff --git a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx index b82292ef..97f8bffa 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx @@ -42,8 +42,6 @@ import { Map } from "maplibre-gl"; import { LabelStatus } from "@/enums/training-area"; import { JOSMLogo, OSMLogo } from "@/assets/svgs"; - - type LabelState = { isFetching: boolean; error: boolean; @@ -52,8 +50,7 @@ type LabelState = { timeSince: string; errorToastShown: boolean; shouldPoll: boolean; -} - +}; export function useInterval(callback: () => void, delay: number | null) { const savedCallback = useRef(callback); @@ -69,53 +66,66 @@ export function useInterval(callback: () => void, delay: number | null) { }, [delay]); } - -const TrainingAreaItem: React.FC = memo(({ datasetId, offset, map, ...trainingArea }) => { - +const TrainingAreaItem: React.FC< + TTrainingAreaFeature & { + datasetId: number; + offset: number; + map: Map | null; + } +> = memo(({ datasetId, offset, map, ...trainingArea }) => { const initialLabelState: LabelState = { isFetching: false, error: false, fetchedDate: trainingArea?.properties?.label_fetched || "", - status: trainingArea?.properties?.label_status || LabelStatus.NOT_DOWNLOADED, + status: + trainingArea?.properties?.label_status || LabelStatus.NOT_DOWNLOADED, timeSince: "", errorToastShown: false, - shouldPoll: false + shouldPoll: false, }; const [labelState, setLabelState] = useState(initialLabelState); - const { onDropdownHide, onDropdownShow, dropdownIsOpened } = useDropdownMenu(); + const { onDropdownHide, onDropdownShow, dropdownIsOpened } = + useDropdownMenu(); const { isOpened, openDialog, closeDialog } = useDialog(); const { formData } = useModelsContext(); const pollingTimeoutRef = useRef(0); - const getTrainingAreaLabels = useGetTrainingAreaLabels(trainingArea.id, false); - const getTrainingArea = useGetTrainingArea(trainingArea.id, labelState.shouldPoll, TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS); + const getTrainingAreaLabels = useGetTrainingAreaLabels( + trainingArea.id, + false, + ); + const getTrainingArea = useGetTrainingArea( + trainingArea.id, + labelState.shouldPoll, + TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS, + ); const createTrainingLabelsForAOI = useCreateTrainingLabelsForAOI({}); - - useInterval(() => { - if (labelState.shouldPoll) { - getTrainingArea.refetch(); - } - }, labelState.shouldPoll ? TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS : null); - + useInterval( + () => { + if (labelState.shouldPoll) { + getTrainingArea.refetch(); + } + }, + labelState.shouldPoll ? TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS : null, + ); useEffect(() => { const updateTimeSince = () => { if (labelState.fetchedDate) { - setLabelState(prev => ({ + setLabelState((prev) => ({ ...prev, - timeSince: formatDuration(new Date(prev.fetchedDate), new Date(), 1) + timeSince: formatDuration(new Date(prev.fetchedDate), new Date(), 1), })); } }; updateTimeSince(); - const intervalId = setInterval(updateTimeSince, TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS); + const intervalId = setInterval( + updateTimeSince, + TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS, + ); return () => clearInterval(intervalId); }, [labelState.fetchedDate]); @@ -123,12 +133,18 @@ const TrainingAreaItem: React.FC { - if (getTrainingArea.isError || getTrainingArea.data?.properties.label_status === LabelStatus.NOT_DOWNLOADED) { + if ( + getTrainingArea.isError || + getTrainingArea.data?.properties.label_status === + LabelStatus.NOT_DOWNLOADED + ) { handleLabelError(); - } else if (getTrainingArea.data?.properties.label_status === LabelStatus.DOWNLOADED) { + } else if ( + getTrainingArea.data?.properties.label_status === LabelStatus.DOWNLOADED + ) { handleLabelSuccess(getTrainingArea.data.properties.label_fetched); } else { - setLabelState(prev => ({ ...prev, status: LabelStatus.DOWNLOADING })); + setLabelState((prev) => ({ ...prev, status: LabelStatus.DOWNLOADING })); } }; @@ -136,7 +152,7 @@ const TrainingAreaItem: React.FC { - setLabelState(prev => ({ + setLabelState((prev) => ({ ...prev, isFetching: true, error: false, @@ -147,34 +163,37 @@ const TrainingAreaItem: React.FC { - setLabelState(prev => ({ ...prev, isFetching: false })); + setLabelState((prev) => ({ ...prev, isFetching: false })); }, onError: handleLabelError, - } + }, ); }, [trainingArea.id]); const handleLabelSuccess = (fetchedDate: string) => { - setLabelState(prev => ({ + setLabelState((prev) => ({ ...prev, fetchedDate, status: LabelStatus.DOWNLOADED, isFetching: false, shouldPoll: false, errorToastShown: false, - timeSince: fetchedDate ? formatDuration(new Date(fetchedDate), new Date(), 1) : "" + timeSince: fetchedDate + ? formatDuration(new Date(fetchedDate), new Date(), 1) + : "", })); }; useEffect(() => { if (!labelState.isFetching) return; - if (getTrainingArea.data?.properties.label_status === LabelStatus.DOWNLOADED) { + if ( + getTrainingArea.data?.properties.label_status === LabelStatus.DOWNLOADED + ) { handleLabelSuccess(getTrainingArea.data.properties.label_fetched); } }, [getTrainingArea.data, labelState.isFetching]); - useEffect(() => { if (labelState.shouldPoll) { const pollInterval = setInterval(() => { @@ -185,20 +204,22 @@ const TrainingAreaItem: React.FC { clearTimeout(pollingTimeoutRef.current); - setLabelState(prev => ({ + setLabelState((prev) => ({ ...prev, isFetching: false, error: true, status: LabelStatus.NOT_DOWNLOADED, - shouldPoll: false + shouldPoll: false, })); if (!labelState.errorToastShown) { - showErrorToast(undefined, `Could not fetch labels for AOI ${trainingArea.id}. Please retry.`); - setLabelState(prev => ({ ...prev, errorToastShown: true })); + showErrorToast( + undefined, + `Could not fetch labels for AOI ${trainingArea.id}. Please retry.`, + ); + setLabelState((prev) => ({ ...prev, errorToastShown: true })); } }; @@ -208,17 +229,17 @@ const TrainingAreaItem: React.FC { - setLabelState(prev => ({ + setLabelState((prev) => ({ ...prev, isFetching: true, error: false, - status: LabelStatus.DOWNLOADING + status: LabelStatus.DOWNLOADING, })); }, onSuccess: (data) => { showSuccessToast(`${data}`); }, - onError: handleLabelError + onError: handleLabelError, }, }); @@ -234,7 +255,6 @@ const TrainingAreaItem: React.FC { if (trainingArea.geometry) { const bounds = getGeoJSONFeatureBounds(trainingArea); @@ -252,85 +272,97 @@ const TrainingAreaItem: React.FC [ - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openINJOSM, - isIcon: false, - imageSrc: JOSMLogo, - onClick: () => openInJOSM(formData.oamTileName, formData.tmsURL, [trainingArea]), - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openInIdEditor, - isIcon: false, - imageSrc: OSMLogo, - onClick: () => openInIDEditor( - formData.oamBounds[1], - formData.oamBounds[3], - formData.oamBounds[0], - formData.oamBounds[2], - formData.tmsURL, - formData.selectedTrainingDatasetId, - trainingArea.id, - ), - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadAOI, - isIcon: true, - Icon: CloudDownloadIcon, - onClick: () => { - geoJSONDowloader(trainingArea, `AOI_${trainingArea.id}`); - showSuccessToast(TOAST_NOTIFICATIONS.aoiDownloadSuccess); - onDropdownHide(); + const dropdownMenuItems = useMemo( + () => [ + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openINJOSM, + isIcon: false, + imageSrc: JOSMLogo, + onClick: () => + openInJOSM(formData.oamTileName, formData.tmsURL, [trainingArea]), }, - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadLabels, - isIcon: true, - Icon: CloudDownloadIcon, - onClick: async () => { - const res = await getTrainingAreaLabels.refetch(); - if (res.isSuccess) { - geoJSONDowloader(res.data, `AOI_${trainingArea.id}_Labels`); + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openInIdEditor, + isIcon: false, + imageSrc: OSMLogo, + onClick: () => + openInIDEditor( + formData.oamBounds[1], + formData.oamBounds[3], + formData.oamBounds[0], + formData.oamBounds[2], + formData.tmsURL, + formData.selectedTrainingDatasetId, + trainingArea.id, + ), + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadAOI, + isIcon: true, + Icon: CloudDownloadIcon, + onClick: () => { + geoJSONDowloader(trainingArea, `AOI_${trainingArea.id}`); + showSuccessToast(TOAST_NOTIFICATIONS.aoiDownloadSuccess); onDropdownHide(); - showSuccessToast(TOAST_NOTIFICATIONS.aoiLabelsDownloadSuccess); - } - if (res.isError) showErrorToast(res.error); + }, }, - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.uploadLabels, - isIcon: true, - Icon: UploadIcon, - onClick: openDialog, - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.deleteAOI, - isIcon: true, - Icon: DeleteIcon, - isDelete: true, - onClick: () => deleteTrainingAreaMutation.mutate({ trainingAreaId: trainingArea.id }), - }, - ], [formData, trainingArea, onDropdownHide, getTrainingAreaLabels]); + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadLabels, + isIcon: true, + Icon: CloudDownloadIcon, + onClick: async () => { + const res = await getTrainingAreaLabels.refetch(); + if (res.isSuccess) { + geoJSONDowloader(res.data, `AOI_${trainingArea.id}_Labels`); + onDropdownHide(); + showSuccessToast(TOAST_NOTIFICATIONS.aoiLabelsDownloadSuccess); + } + if (res.isError) showErrorToast(res.error); + }, + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.uploadLabels, + isIcon: true, + Icon: UploadIcon, + onClick: openDialog, + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.deleteAOI, + isIcon: true, + Icon: DeleteIcon, + isDelete: true, + onClick: () => + deleteTrainingAreaMutation.mutate({ + trainingAreaId: trainingArea.id, + }), + }, + ], + [formData, trainingArea, onDropdownHide, getTrainingAreaLabels], + ); - const trainingAreaSize = useMemo(() => - trainingArea.geometry ? formatAreaInAppropriateUnit(calculateGeoJSONArea(trainingArea)) : "0 m²", - [trainingArea] + const trainingAreaSize = useMemo( + () => + trainingArea.geometry + ? formatAreaInAppropriateUnit(calculateGeoJSONArea(trainingArea)) + : "0 m²", + [trainingArea], ); const fetchStatusInfo = useMemo(() => { if (labelState.isFetching || trainingAreaLabelsMutation.isPending) { - return 'Fetching labels...'; + return "Fetching labels..."; } if (labelState.error) { - return 'Error occurred. Please retry.'; + return "Error occurred. Please retry."; } if (labelState.status === LabelStatus.DOWNLOADED) { - return labelState.timeSince ? `Fetched ${labelState.timeSince} ago` : 'Fetched recently'; + return labelState.timeSince + ? `Fetched ${labelState.timeSince} ago` + : "Fetched recently"; } - return 'No labels yet'; + return "No labels yet"; }, [labelState, trainingAreaLabelsMutation.isPending]); - return ( <> Area: {truncateString(trainingAreaSize, 15)}

-

+

{fetchStatusInfo}

- + - + @@ -409,5 +455,4 @@ const TrainingAreaItem: React.FC
) : (
- {data?.results.features.sort((a, b) => b.id - a.id).map((ta) => ( - - ))} + {data?.results.features + .sort((a, b) => b.id - a.id) + .map((ta) => ( + + ))}
)}
diff --git a/frontend/src/features/model-creation/hooks/use-polling.ts b/frontend/src/features/model-creation/hooks/use-polling.ts index 32e534a0..5d11da83 100644 --- a/frontend/src/features/model-creation/hooks/use-polling.ts +++ b/frontend/src/features/model-creation/hooks/use-polling.ts @@ -8,12 +8,12 @@ type PollingHookReturn = { startPolling: () => void; stopPolling: () => void; error: boolean; -} +}; export const usePolling = ( taskId: number, fetchTaskStatus: (id: number) => Promise, - interval: number = 5000 + interval: number = 5000, ): PollingHookReturn => { const [status, setStatus] = useState(LabelStatus.NOT_DOWNLOADED); const [isPolling, setIsPolling] = useState(false); diff --git a/frontend/src/features/model-creation/hooks/use-training-areas.ts b/frontend/src/features/model-creation/hooks/use-training-areas.ts index fb931c43..a9182eef 100644 --- a/frontend/src/features/model-creation/hooks/use-training-areas.ts +++ b/frontend/src/features/model-creation/hooks/use-training-areas.ts @@ -22,7 +22,7 @@ export const useGetTrainingAreas = (datasetId: number, offset: number) => { return useQuery({ ...getTrainingAreasQueryOptions(datasetId, offset), //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500 + throwOnError: (error) => error?.response?.status >= 500, }); }; @@ -154,8 +154,11 @@ export const useGetTrainingAreaLabels = (aoiId: number, enabled: boolean) => { }); }; - -export const useGetTrainingArea = (aoiId: number, enabled: boolean, refetchInterval: number) => { +export const useGetTrainingArea = ( + aoiId: number, + enabled: boolean, + refetchInterval: number, +) => { return useQuery({ ...getTrainingAreaQueryOptions(aoiId), //@ts-expect-error bad type definition diff --git a/frontend/src/features/models/api/factory.ts b/frontend/src/features/models/api/factory.ts index ace27f3f..b3767a88 100644 --- a/frontend/src/features/models/api/factory.ts +++ b/frontend/src/features/models/api/factory.ts @@ -57,12 +57,15 @@ export const getModelsQueryOptions = ({ }); }; -export const getModelDetailsQueryOptions = (id: string, refetchInterval: boolean | number) => { +export const getModelDetailsQueryOptions = ( + id: string, + refetchInterval: boolean | number, +) => { return queryOptions({ queryKey: [queryKeys.MODEL_DETAILS(id)], queryFn: () => getModelDetails(id), //@ts-expect-error bad type definition - refetchInterval: refetchInterval + refetchInterval: refetchInterval, }); }; diff --git a/frontend/src/features/models/components/dialogs/model-files-dialog.tsx b/frontend/src/features/models/components/dialogs/model-files-dialog.tsx index 3767dfcd..6e35e6db 100644 --- a/frontend/src/features/models/components/dialogs/model-files-dialog.tsx +++ b/frontend/src/features/models/components/dialogs/model-files-dialog.tsx @@ -20,7 +20,12 @@ const ModelFilesDialog: React.FC = ({ closeDialog={closeDialog} label={APP_CONTENT.models.modelsDetailsCard.modelFilesDialog.dialogTitle} > -

{APP_CONTENT.models.modelsDetailsCard.modelFilesDialog.dialogDescription}

+

+ { + APP_CONTENT.models.modelsDetailsCard.modelFilesDialog + .dialogDescription + } +

{isOpened && ( = ({ const fetchDirectoryRecursive = async ( currentDirectory: string = "", currentDepth: number = 0, - maxDepth: number = 2 + maxDepth: number = 2, ): Promise => { if (currentDepth >= maxDepth) { - return {}; } diff --git a/frontend/src/features/models/components/filters/status-filter.tsx b/frontend/src/features/models/components/filters/status-filter.tsx index 9c4d61f3..adda2ff4 100644 --- a/frontend/src/features/models/components/filters/status-filter.tsx +++ b/frontend/src/features/models/components/filters/status-filter.tsx @@ -95,7 +95,6 @@ const StatusFilter: React.FC = ({ options={statusCategories} disabled={disabled} onCheck={(status) => { - console.log(status); updateQuery({ [SEARCH_PARAMS.status]: status[0] !== "All" ? status[0] : undefined, }); diff --git a/frontend/src/features/models/components/maps/training-area-map.tsx b/frontend/src/features/models/components/maps/training-area-map.tsx index 88780030..b9a23f23 100644 --- a/frontend/src/features/models/components/maps/training-area-map.tsx +++ b/frontend/src/features/models/components/maps/training-area-map.tsx @@ -182,15 +182,15 @@ export const TrainingAreaMap = ({ ${Object.entries(feature.properties) - .map( - ([key, value]) => ` + .map( + ([key, value]) => ` `, - ) - .join("")} + ) + .join("")}
${key} ${typeof value === "boolean" ? JSON.stringify(value) : value}
diff --git a/frontend/src/features/models/components/model-card.tsx b/frontend/src/features/models/components/model-card.tsx index 82dbea70..fff0e558 100644 --- a/frontend/src/features/models/components/model-card.tsx +++ b/frontend/src/features/models/components/model-card.tsx @@ -57,10 +57,10 @@ const ModelCard: React.FC = ({ model }) => {
)}
-
-
-

- {truncateString(model.name, 20)} +

+
+

+ {truncateString(model.name, 50)}

ID: {model.id} @@ -78,7 +78,7 @@ const ModelCard: React.FC = ({ model }) => { {/* Status badge */} {/* Name, date and base model */} -

+

{model.user.username}

diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index 988211c6..dd5d39e1 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -43,11 +43,12 @@ const ModelDetailsPopUp = ({ active={showPopup} anchor={anchor} placement="bottom-start" - distance={10} + // Distance is based on the navbar height. + distance={40} > { -
+
{!model && isError ? (
{startMappingPageContent.modelDetails.error}
) : ( @@ -60,48 +61,50 @@ const ModelDetailsPopUp = ({ > ✕ -

{startMappingPageContent.modelDetails.label}

+

+ {startMappingPageContent.modelDetails.label} +

-
-

+

+

{" "} {startMappingPageContent.modelDetails.popover.modelId}:{" "} - {model?.id ?? data?.id} + {model?.id ?? data?.id}

-

+

{startMappingPageContent.modelDetails.popover.description}:{" "} - + {model?.description ?? data?.description}

-

+

{startMappingPageContent.modelDetails.popover.lastModified}:{" "} - + {extractDatePart( model?.last_modified ?? (data?.last_modified as string), )}

-

+

{startMappingPageContent.modelDetails.popover.trainingId}:{" "} - + {model?.published_training ?? data?.published_training}

-

+

{startMappingPageContent.modelDetails.popover.datasetId}:{" "} - + {model?.dataset ?? data?.dataset}

-

+

{startMappingPageContent.modelDetails.popover.datasetName}:{" "} {trainingDatasetIsError @@ -111,7 +114,7 @@ const ModelDetailsPopUp = ({

-

+

{startMappingPageContent.modelDetails.popover.zoomLevel}:{" "}

-

+

{startMappingPageContent.modelDetails.popover.accuracy}:{" "} - + {roundNumber( model?.accuracy ?? (data?.accuracy as number), 2, @@ -135,9 +138,9 @@ const ModelDetailsPopUp = ({ %

-

+

{startMappingPageContent.modelDetails.popover.baseModel}:{" "} - + {model?.base_model ?? data?.base_model}

diff --git a/frontend/src/features/models/components/model-details-properties.tsx b/frontend/src/features/models/components/model-details-properties.tsx index 5086a714..a15d3cf0 100644 --- a/frontend/src/features/models/components/model-details-properties.tsx +++ b/frontend/src/features/models/components/model-details-properties.tsx @@ -25,6 +25,7 @@ enum TrainingStatus { IN_PROGRESS = "IN PROGRESS", RUNNING = "RUNNING", SUCCESS = "SUCCESS", + FINISHED = "FINISHED", } type PropertyDisplayProps = { @@ -288,10 +289,7 @@ const ModelProperties: React.FC = ({ {isTrainingDetailsDialog && (
@@ -300,7 +298,7 @@ const ModelProperties: React.FC = ({ {isTrainingDetailsDialog && ( )}
diff --git a/frontend/src/features/models/components/training-history-table.tsx b/frontend/src/features/models/components/training-history-table.tsx index e35fb61a..498e84aa 100644 --- a/frontend/src/features/models/components/training-history-table.tsx +++ b/frontend/src/features/models/components/training-history-table.tsx @@ -25,7 +25,6 @@ import { useUpdateTraining } from "@/features/models/api/update-trainings"; import { Pagination, PAGE_LIMIT } from "@/components/shared"; import { useToastNotification } from "@/hooks/use-toast-notification"; - type TrainingHistoryTableProps = { modelId: string; trainingId: number; @@ -43,121 +42,121 @@ const columnDefinitions = ( handleTrainingModal: (trainingId: number) => void, publishTraining: (trainingId: number) => void, ): ColumnDef[] => [ - { - accessorKey: "id", - header: ({ column }) => , - }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader - .epochAndBatchSize, - accessorFn: (row) => `${row.epochs}/${row.batch_size}`, - cell: (row) => ( - {row.getValue() as string} - ), + { + accessorKey: "id", + header: ({ column }) => , + }, + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader + .epochAndBatchSize, + accessorFn: (row) => `${row.epochs}/${row.batch_size}`, + cell: (row) => ( + {row.getValue() as string} + ), + }, + { + accessorKey: "started_at", + accessorFn: (row) => + row.started_at !== null ? formatDate(row.started_at) : "-", + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.startedAt, + cell: (row) => { + return {row.getValue() as string}; }, - { - accessorKey: "started_at", - accessorFn: (row) => - row.started_at !== null ? formatDate(row.started_at) : "-", - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.startedAt, - cell: (row) => { - return {row.getValue() as string}; - }, + }, + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.duration, + accessorFn: (row) => + row.finished_at && row.started_at + ? formatDuration(new Date(row.started_at), new Date(row.finished_at)) + : "-", + cell: (row) => ( + {row.getValue() as string} + ), + }, + { + accessorKey: "user.username", + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader + .sumittedBy, + cell: ({ row }) => { + return {truncateString(row.original.user.username)}; }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.duration, - accessorFn: (row) => - row.finished_at && row.started_at - ? formatDuration(new Date(row.started_at), new Date(row.finished_at)) - : "-", - cell: (row) => ( - {row.getValue() as string} - ), - }, - { - accessorKey: "user.username", - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader - .sumittedBy, - cell: ({ row }) => { - return {truncateString(row.original.user.username)}; - }, + }, + { + accessorKey: "chips_length", + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.dsSize, + cell: ({ row }) => { + return {row.getValue("chips_length") ?? 0}; }, - { - accessorKey: "chips_length", - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.dsSize, - cell: ({ row }) => { - return {row.getValue("chips_length") ?? 0}; - }, - }, - { - accessorKey: "accuracy", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return ( - - {Number(row.getValue("accuracy")) > 0 - ? roundNumber(row.getValue("accuracy") ?? 0) - : "-"} - - ); - }, + }, + { + accessorKey: "accuracy", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( + + {Number(row.getValue("accuracy")) > 0 + ? roundNumber(row.getValue("accuracy") ?? 0) + : "-"} + + ); }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.status, - accessorKey: "status", - cell: (row) => { - const statusToVariant: Record = { - finished: "green", - failed: "red", - submitted: "blue", - running: "yellow", - }; + }, + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.status, + accessorKey: "status", + cell: (row) => { + const statusToVariant: Record = { + finished: "green", + failed: "red", + submitted: "blue", + running: "yellow", + }; - return ( - - {String(row.getValue()).toLocaleLowerCase() as string} - - ); - }, + ] + } + > + {String(row.getValue()).toLocaleLowerCase() as string} + + ); }, - { - header: - APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.inUse, + }, + { + header: + APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader.inUse, - cell: ({ row }) => { - return ( - - {row.getValue("id") === trainingId ? ( - - - - ) : null} - - ); - }, + cell: ({ row }) => { + return ( + + {row.getValue("id") === trainingId ? ( + + + + ) : null} + + ); }, - ...(modelOwner !== authUsername - ? [ + }, + ...(modelOwner !== authUsername + ? [ { header: APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader @@ -176,9 +175,9 @@ const columnDefinitions = ( }, }, ] - : []), - ...(modelOwner === authUsername && isAuthenticated - ? [ + : []), + ...(modelOwner === authUsername && isAuthenticated + ? [ { header: APP_CONTENT.models.modelsDetailsCard.trainingHistoryTableHeader @@ -227,8 +226,8 @@ const columnDefinitions = ( }, }, ] - : []), - ]; + : []), +]; const TrainingHistoryTable: React.FC = ({ trainingId, diff --git a/frontend/src/features/models/hooks/use-models.ts b/frontend/src/features/models/hooks/use-models.ts index 49506ad4..0d58d627 100644 --- a/frontend/src/features/models/hooks/use-models.ts +++ b/frontend/src/features/models/hooks/use-models.ts @@ -52,7 +52,11 @@ export const useModels = ({ }); }; -export const useModelDetails = (id: string, enabled: boolean = true, refetchInterval: boolean | number = false) => { +export const useModelDetails = ( + id: string, + enabled: boolean = true, + refetchInterval: boolean | number = false, +) => { return useQuery({ ...getModelDetailsQueryOptions(id, refetchInterval), //@ts-expect-error bad type definition diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index 9f6d0d1e..07045216 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -18,10 +18,9 @@ import { geoJSONDowloader, openInJOSM, showSuccessToast, - truncateString, } from "@/utils"; -import { useCallback, useState } from "react"; -import ModelSettings from "./model-settings"; +import { useCallback, useMemo, useState } from "react"; +import { ModelSettings } from "./model-settings"; import { TQueryParams } from "@/app/routes/start-mapping"; import ModelAction from "./model-action"; import { TModelPredictionsConfig } from "../api/get-model-predictions"; @@ -30,21 +29,25 @@ import { NavLogo, UserProfile } from "@/components/layout"; import { useNavigate } from "react-router-dom"; import { startMappingPageContent } from "@/constants"; import { Map } from "maplibre-gl"; +import { ToolTip } from "@/components/ui/tooltip"; +import { ModelDetailsButton } from "./model-details-button"; const StartMappingHeader = ({ data, modelPredictions, oamTileJSON, trainingDataset, - trainingDatasetIsError, modelPredictionsExist, trainingDatasetIsPending, query, updateQuery, trainingConfig, setModelPredictions, - currentZoom, + disablePrediction, map, + popupAnchorId, + setShowModelDetails, + showModelDetails }: { modelPredictionsExist: boolean; trainingDatasetIsPending: boolean; @@ -57,9 +60,14 @@ const StartMappingHeader = ({ updateQuery: (newParams: TQueryParams) => void; trainingConfig: TModelPredictionsConfig; setModelPredictions: React.Dispatch>; - currentZoom: number; map: Map | null; + disablePrediction: boolean; + popupAnchorId: string + setShowModelDetails: (x: boolean) => void + showModelDetails: boolean }) => { + const navigate = useNavigate(); + const { onDropdownHide, onDropdownShow, dropdownIsOpened } = useDropdownMenu(); @@ -69,10 +77,6 @@ const StartMappingHeader = ({ dropdownIsOpened: FAIRLogoDropdownIsOpened, } = useDropdownMenu(); - const [showModelDetails, setShowModelDetails] = useState(false); - - const popupAnchorId = "model-details"; - const handleAllFeaturesDownload = useCallback(async () => { geoJSONDowloader( { @@ -118,47 +122,55 @@ const StartMappingHeader = ({ handleFeaturesDownloadToJOSM(modelPredictions.accepted); }, [handleFeaturesDownloadToJOSM, modelPredictions.accepted]); - const downloadButtonDropdownOptions = [ - { - name: startMappingPageContent.buttons.download.options.allFeatures, - value: startMappingPageContent.buttons.download.options.allFeatures, - onClick: handleAllFeaturesDownload, - }, - { - name: startMappingPageContent.buttons.download.options.acceptedFeatures, - value: startMappingPageContent.buttons.download.options.acceptedFeatures, - onClick: handleAcceptedFeaturesDownload, - }, - { - name: startMappingPageContent.buttons.download.options - .openAllFeaturesInJOSM, - value: - startMappingPageContent.buttons.download.options.openAllFeaturesInJOSM, - onClick: handleAllFeaturesDownloadToJOSM, - }, - { - name: startMappingPageContent.buttons.download.options - .openAcceptedFeaturesInJOSM, - value: - startMappingPageContent.buttons.download.options + const downloadButtonDropdownOptions = useMemo( + () => [ + { + name: startMappingPageContent.buttons.download.options.allFeatures, + value: startMappingPageContent.buttons.download.options.allFeatures, + onClick: handleAllFeaturesDownload, + }, + { + name: startMappingPageContent.buttons.download.options.acceptedFeatures, + value: + startMappingPageContent.buttons.download.options.acceptedFeatures, + onClick: handleAcceptedFeaturesDownload, + }, + { + name: startMappingPageContent.buttons.download.options + .openAllFeaturesInJOSM, + value: + startMappingPageContent.buttons.download.options + .openAllFeaturesInJOSM, + onClick: handleAllFeaturesDownloadToJOSM, + }, + { + name: startMappingPageContent.buttons.download.options .openAcceptedFeaturesInJOSM, - onClick: handleAcceptedFeaturesDownloadToJOSM, - }, - ]; - const navigate = useNavigate(); + value: + startMappingPageContent.buttons.download.options + .openAcceptedFeaturesInJOSM, + onClick: handleAcceptedFeaturesDownloadToJOSM, + }, + ], + [ + startMappingPageContent, + handleAcceptedFeaturesDownloadToJOSM, + handleAllFeaturesDownloadToJOSM, + handleAcceptedFeaturesDownload, + handleAllFeaturesDownload, + ], + ); return ( -
+
null} width={"70px"} height={"50px"} /> - } + triggerComponent={ null} width="45px" />} >
@@ -177,33 +189,20 @@ const StartMappingHeader = ({
-
-

- {data?.name ? truncateString(data?.name, 35) : "N/A"} -

- setShowModelDetails(false)} - anchor={popupAnchorId} - model={data} - trainingDataset={trainingDataset} - trainingDatasetIsPending={trainingDatasetIsPending} - trainingDatasetIsError={trainingDatasetIsError} - /> - + {data?.name ?? "N/A"} +

+ setShowModelDetails(!showModelDetails)} showModelDetails={showModelDetails} popupAnchorId={popupAnchorId} />
-
+
-

- {startMappingPageContent.mapData.title} -{" "} +

{startMappingPageContent.mapData.accepted}:{" "} {modelPredictions.accepted.length}{" "} {startMappingPageContent.mapData.rejected}:{" "} @@ -217,18 +216,26 @@ const StartMappingHeader = ({ onDropdownShow={onDropdownShow} menuItems={downloadButtonDropdownOptions} triggerComponent={ - + > + + } />

@@ -237,10 +244,9 @@ const StartMappingHeader = ({ setModelPredictions={setModelPredictions} trainingConfig={trainingConfig} map={map} - currentZoom={currentZoom} + disablePrediction={disablePrediction} /> - - +
diff --git a/frontend/src/features/start-mapping/components/index.ts b/frontend/src/features/start-mapping/components/index.ts index 485b2d86..2a7277e0 100644 --- a/frontend/src/features/start-mapping/components/index.ts +++ b/frontend/src/features/start-mapping/components/index.ts @@ -1,5 +1,7 @@ export { default as StartMappingMapComponent } from "./map"; export { default as PredictedFeatureActionPopup } from "./popup"; export { default as StartMappingHeader } from "./header"; -export { default as ModelSettings } from "./model-settings"; +export { ModelSettings } from "./model-settings"; export { default as ModelAction } from "./model-action"; +export { StartMappingMobileDrawer } from "./mobile-drawer"; +export { ModelDetailsButton } from './model-details-button' \ No newline at end of file diff --git a/frontend/src/features/start-mapping/components/mobile-drawer.tsx b/frontend/src/features/start-mapping/components/mobile-drawer.tsx new file mode 100644 index 00000000..8634dfed --- /dev/null +++ b/frontend/src/features/start-mapping/components/mobile-drawer.tsx @@ -0,0 +1,42 @@ +import { MobileDrawer } from "@/components/ui/drawer" +import { MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION } from "@/utils" +import ModelAction from "./model-action" +import { TModelPredictionsConfig } from "../api/get-model-predictions"; +import { TModelPredictions } from "@/types"; +import { Map } from "maplibre-gl"; +import { ModelDetailsButton } from "./model-details-button"; + + +export const StartMappingMobileDrawer = ( + { isOpen, disablePrediction, trainingConfig, setModelPredictions, map, modelPredictions, showModelDetails, setShowModelDetails }: + { + isOpen: boolean, + disablePrediction: boolean, + trainingConfig: TModelPredictionsConfig; + modelPredictions: TModelPredictions; + setModelPredictions: React.Dispatch>; + map: Map | null; + showModelDetails: boolean + setShowModelDetails: (x: boolean) => void + }) => { + + return ( + + {disablePrediction &&

{MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION}

} +
+
+ +
+
+ setShowModelDetails(!showModelDetails)} showModelDetails={showModelDetails} /> +
+
+
+ ) +} \ No newline at end of file diff --git a/frontend/src/features/start-mapping/components/model-action.tsx b/frontend/src/features/start-mapping/components/model-action.tsx index 5e76f52a..4ef180e1 100644 --- a/frontend/src/features/start-mapping/components/model-action.tsx +++ b/frontend/src/features/start-mapping/components/model-action.tsx @@ -1,35 +1,26 @@ import { TModelPredictions } from "@/types"; -import { - handleConflation, - MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION, - showErrorToast, - showSuccessToast, -} from "@/utils"; +import { handleConflation, showErrorToast, showSuccessToast } from "@/utils"; import { useGetModelPredictions } from "@/features/start-mapping/hooks/use-model-predictions"; -import { Button } from "@/components/ui/button"; import { startMappingPageContent, TOAST_NOTIFICATIONS } from "@/constants"; import { useCallback } from "react"; import { TModelPredictionsConfig } from "../api/get-model-predictions"; -import { SHOELACE_SIZES } from "@/enums"; import { Map } from "maplibre-gl"; +import { ToolTip } from "@/components/ui/tooltip"; const ModelAction = ({ setModelPredictions, modelPredictions, trainingConfig, map, - currentZoom, + disablePrediction, }: { trainingConfig: TModelPredictionsConfig; modelPredictions: TModelPredictions; setModelPredictions: React.Dispatch>; map: Map | null; - currentZoom: number; + disablePrediction: boolean; }) => { - const disablePredictionButton = - currentZoom < MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION; - const modelPredictionMutation = useGetModelPredictions({ mutationConfig: { onSuccess: (data) => { @@ -53,17 +44,24 @@ const ModelAction = ({ return (
- + +
); }; diff --git a/frontend/src/features/start-mapping/components/model-details-button.tsx b/frontend/src/features/start-mapping/components/model-details-button.tsx new file mode 100644 index 00000000..4dc58962 --- /dev/null +++ b/frontend/src/features/start-mapping/components/model-details-button.tsx @@ -0,0 +1,17 @@ +import { TagsInfoIcon } from "@/components/ui/icons" +import { ToolTip } from "@/components/ui/tooltip" +import { startMappingPageContent } from "@/constants" + +export const ModelDetailsButton = ({ popupAnchorId, onClick, showModelDetails = false }: { popupAnchorId?: string, onClick: () => void, showModelDetails?: boolean }) => { + return ( + + + + ) +} \ No newline at end of file diff --git a/frontend/src/features/start-mapping/components/model-settings.tsx b/frontend/src/features/start-mapping/components/model-settings.tsx index b2aafc42..a6b8738e 100644 --- a/frontend/src/features/start-mapping/components/model-settings.tsx +++ b/frontend/src/features/start-mapping/components/model-settings.tsx @@ -1,11 +1,12 @@ import { SEARCH_PARAMS, TQueryParams } from "@/app/routes/start-mapping"; -import { ButtonWithIcon } from "@/components/ui/button"; import { DropDown } from "@/components/ui/dropdown"; import { FormLabel, Input, Select, Switch } from "@/components/ui/form"; -import { ChevronDownIcon } from "@/components/ui/icons"; +import { SettingsIcon } from "@/components/ui/icons"; +import { ToolTip } from "@/components/ui/tooltip"; import { startMappingPageContent } from "@/constants"; import { INPUT_TYPES, SHOELACE_SIZES } from "@/enums"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; +import { memo } from "react"; const confidenceLevels = [ { @@ -26,131 +27,138 @@ const confidenceLevels = [ }, ]; -const ModelSettings = ({ - query, - updateQuery, -}: { - query: TQueryParams; - updateQuery: (newParams: TQueryParams) => void; -}) => { - const { - onDropdownHide: onModelSettingsDropdownHide, - onDropdownShow: onModelSettingsDropdownShow, - dropdownIsOpened, - } = useDropdownMenu(); +export const ModelSettings = memo( + ({ + query, + updateQuery, + }: { + query: TQueryParams; + updateQuery: (newParams: TQueryParams) => void; + }) => { + const { + onDropdownHide: onModelSettingsDropdownHide, + onDropdownShow: onModelSettingsDropdownShow, + dropdownIsOpened, + toggleDropDown, + } = useDropdownMenu(); - const handleQueryUpdate = (key: string, val: number | boolean) => { - // Keep the dropdown opened when making changes - onModelSettingsDropdownShow(); - updateQuery({ - [key]: val, - }); - }; + const handleQueryUpdate = (key: string, val: number | boolean) => { + // Keep the dropdown opened when making changes + onModelSettingsDropdownShow(); + updateQuery({ + [key]: val, + }); + }; - return ( - - } - > -
-
- - { - handleQueryUpdate(SEARCH_PARAMS.useJOSMQ, event.target.checked); - }} - /> + return ( + + + + } + className="rounded-xl" + > +
+
+ + { + handleQueryUpdate(SEARCH_PARAMS.useJOSMQ, event.target.checked); + }} + /> +
+
+ + + handleQueryUpdate( + SEARCH_PARAMS.tolerance, + Number(event.target.value), + ) + } + min={0} + step={0.1} + /> +
+
+ + + handleQueryUpdate( + SEARCH_PARAMS.area, + Number(event.target.value), + ) + } + min={0} + /> +
-
- - - handleQueryUpdate( - SEARCH_PARAMS.tolerance, - Number(event.target.value), - ) - } - min={0} - step={0.1} - /> -
-
- - - handleQueryUpdate(SEARCH_PARAMS.area, Number(event.target.value)) - } - min={0} - /> -
-
- - ); -}; - -export default ModelSettings; + + ); + }, +); diff --git a/frontend/src/hooks/use-dropdown-menu.ts b/frontend/src/hooks/use-dropdown-menu.ts index 05be6267..be835d6e 100644 --- a/frontend/src/hooks/use-dropdown-menu.ts +++ b/frontend/src/hooks/use-dropdown-menu.ts @@ -21,6 +21,15 @@ export const useDropdownMenu = () => { const onDropdownHide = useCallback(() => { setIsOpened(false); }, []); + + const toggleDropDown = useCallback(() => { + if (isOpened) { + setIsOpened(false); + } else { + setIsOpened(true); + } + }, [isOpened]); + const dropdownIsOpened = useMemo(() => isOpened, [isOpened]); - return { onDropdownHide, onDropdownShow, dropdownIsOpened }; + return { onDropdownHide, onDropdownShow, dropdownIsOpened, toggleDropDown }; }; diff --git a/frontend/src/hooks/use-map-instance.ts b/frontend/src/hooks/use-map-instance.ts index a55a9493..38703232 100644 --- a/frontend/src/hooks/use-map-instance.ts +++ b/frontend/src/hooks/use-map-instance.ts @@ -5,10 +5,9 @@ import { MAP_STYLES } from "@/utils"; import { Map } from "maplibre-gl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; - /** * useMapInstance - Initializes and manages a MapLibre map instance with TerraDraw integration. - * + * * @param {boolean} pmtiles - Optional flag to enable PMTiles support. * @returns {Object} - Contains map instance, zoom level, drawing mode, and container ref. */ @@ -21,7 +20,11 @@ export const useMapInstance = (pmtiles: boolean = false) => { ); useEffect(() => { - const map = setupMaplibreMap(mapContainerRef, MAP_STYLES[BASEMAPS.OSM], pmtiles); + const map = setupMaplibreMap( + mapContainerRef, + MAP_STYLES[BASEMAPS.OSM], + pmtiles, + ); map.on("load", () => { setMap(map); diff --git a/frontend/src/hooks/use-scroll-to-element.ts b/frontend/src/hooks/use-scroll-to-element.ts index bb2f85b3..c7472e74 100644 --- a/frontend/src/hooks/use-scroll-to-element.ts +++ b/frontend/src/hooks/use-scroll-to-element.ts @@ -16,7 +16,7 @@ export const useScrollToElement = (id: string) => { const scrollToElement = () => { const element = document.getElementById(id); if (element) { - element.scrollIntoView({ behavior: "smooth", block: "center", }); + element.scrollIntoView({ behavior: "smooth", block: "center" }); } }; return { scrollToElement }; diff --git a/frontend/src/layouts/model-forms-layout.tsx b/frontend/src/layouts/model-forms-layout.tsx index 219dc874..823f4d11 100644 --- a/frontend/src/layouts/model-forms-layout.tsx +++ b/frontend/src/layouts/model-forms-layout.tsx @@ -26,43 +26,43 @@ const pages: { icon: React.ElementType; path: string; }[] = [ - { - id: 1, - title: MODEL_CREATION_CONTENT.progressStepper.modelDetails, - icon: TagsIcon, - path: MODELS_ROUTES.DETAILS, - }, - { - id: 2, - title: MODEL_CREATION_CONTENT.progressStepper.trainingDataset, - icon: DatabaseIcon, - path: MODELS_ROUTES.TRAINING_DATASET, - }, - { - id: 3, - title: MODEL_CREATION_CONTENT.progressStepper.trainingArea, - icon: SquareShadowIcon, - path: MODELS_ROUTES.TRAINING_AREA, - }, - { - id: 4, - title: MODEL_CREATION_CONTENT.progressStepper.trainingSettings, - icon: SettingsIcon, - path: MODELS_ROUTES.TRAINING_SETTINGS, - }, - { - id: 5, - title: MODEL_CREATION_CONTENT.progressStepper.submitModel, - icon: CloudIcon, - path: MODELS_ROUTES.MODEL_SUMMARY, - }, - { - id: 6, - title: MODEL_CREATION_CONTENT.progressStepper.confirmation, - icon: StarIcon, - path: MODELS_ROUTES.CONFIRMATION, - }, - ]; + { + id: 1, + title: MODEL_CREATION_CONTENT.progressStepper.modelDetails, + icon: TagsIcon, + path: MODELS_ROUTES.DETAILS, + }, + { + id: 2, + title: MODEL_CREATION_CONTENT.progressStepper.trainingDataset, + icon: DatabaseIcon, + path: MODELS_ROUTES.TRAINING_DATASET, + }, + { + id: 3, + title: MODEL_CREATION_CONTENT.progressStepper.trainingArea, + icon: SquareShadowIcon, + path: MODELS_ROUTES.TRAINING_AREA, + }, + { + id: 4, + title: MODEL_CREATION_CONTENT.progressStepper.trainingSettings, + icon: SettingsIcon, + path: MODELS_ROUTES.TRAINING_SETTINGS, + }, + { + id: 5, + title: MODEL_CREATION_CONTENT.progressStepper.submitModel, + icon: CloudIcon, + path: MODELS_ROUTES.MODEL_SUMMARY, + }, + { + id: 6, + title: MODEL_CREATION_CONTENT.progressStepper.confirmation, + icon: StarIcon, + path: MODELS_ROUTES.CONFIRMATION, + }, +]; export const ModelFormsLayout = () => { const { pathname } = useLocation(); @@ -104,7 +104,6 @@ export const ModelFormsLayout = () => { ); }; - const ModelFormRouteValidator = ({ pathname, currentPageIndex, diff --git a/frontend/src/layouts/root-layout.tsx b/frontend/src/layouts/root-layout.tsx index a55a736b..9321958b 100644 --- a/frontend/src/layouts/root-layout.tsx +++ b/frontend/src/layouts/root-layout.tsx @@ -29,5 +29,3 @@ export const RootLayout = () => { ); }; - - diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index d3da8363..1867db1c 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -1 +1 @@ -export * from './geojson-to-osm' \ No newline at end of file +export * from "./geojson-to-osm"; diff --git a/frontend/src/services/api-routes.ts b/frontend/src/services/api-routes.ts index 3b3cc20a..24d30eaf 100644 --- a/frontend/src/services/api-routes.ts +++ b/frontend/src/services/api-routes.ts @@ -70,7 +70,6 @@ export const API_ENDPOINTS = { UPDATE_TRAINING: (id: number) => `training/publish/${id}/`, DELETE_TRAINING_AREA: (id: number) => `aoi/${id}/`, - // Workspace GET_PMTILES_URL: (trainingAreaId: number) => diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index cbcf8bf5..1243d2b5 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -168,20 +168,23 @@ sl-alert.success::part(base) { .static-page-layout { @apply flex flex-col gap-y-20 md:gap-y-40 mb-48; } + .icon-interaction { + @apply bg-light-gray rounded-lg; + } } /* Tailwind styles ends */ /* General scrollbar styles */ + ::-webkit-scrollbar { - width: var(--scrollbar-width); + width: var(--scrollbar-width); height: 5px; - opacity: 0; - transition: opacity 0.2s ease; + opacity: 0; } ::-webkit-scrollbar:hover { - opacity: 1; + opacity: 1; } /* Scrollbar track */ @@ -193,6 +196,7 @@ sl-alert.success::part(base) { ::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb-bg, #888); border-radius: 5px; + overflow: hidden; } /* Scrollbar thumb on hover */ @@ -200,21 +204,18 @@ sl-alert.success::part(base) { background: var(--scrollbar-thumb-hover-bg, #555); } -/* Hide scrollbar by default for specific elements */ -.scrollable::-webkit-scrollbar { - width: var(--scrollbar-width); - display: none; +.scrollable::-webkit-scrollbar-track, +.scrollable::-webkit-scrollbar-thumb { + visibility: hidden; } -/* Show scrollbar when hovering over the scrollable container */ -.scrollable:hover::-webkit-scrollbar { - display: block; - transition: opacity 1.3s ease, display 1.3s ease; +.scrollable:hover::-webkit-scrollbar-track, +.scrollable:hover::-webkit-scrollbar-thumb { + visibility: visible; } /* General scrollbar ends */ - /* Popup customization starts */ .maplibregl-popup-content { @apply !rounded-[12px] !flex !flex-col !gap-y-4 !p-0; diff --git a/frontend/src/types/ui-contents.ts b/frontend/src/types/ui-contents.ts index a2ab1cf8..776d522d 100644 --- a/frontend/src/types/ui-contents.ts +++ b/frontend/src/types/ui-contents.ts @@ -57,6 +57,7 @@ export type TStartMappingPageContent = { }; buttons: { runPrediction: string; + tooltip: string; download: { label: string; options: { @@ -85,15 +86,20 @@ export type TStartMappingPageContent = { label: string; tooltip: string; }; + tooltip: string; }; mapData: { title: string; accepted: string; rejected: string; }; + actions: { + disabledModeTooltip: string; + }; modelDetails: { error: string; label: string; + tooltip: string; popover: { title: string; modelId: string; diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 5f4ba4d3..e5dad2db 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -92,7 +92,7 @@ const REFRESH_BUFFER_MS = 1000; */ export const KPI_STATS_CACHE_TIME_MS = (Number(ENVS.KPI_STATS_CACHE_TIME) || DEFAULT_KPI_STATS_CACHE_TIME_SECONDS) * - 1000 + + 1000 + REFRESH_BUFFER_MS; /** @@ -256,11 +256,13 @@ export const JOSM_REMOTE_URL = ENVS.JOSM_REMOTE_URL || "http://127.0.0.1:8111/"; * The time to poll the backend for the status of the AOI training labels fetching, in milliseconds (ms). * Defaults to 5000 ms (5 seconds). */ -export const TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS = ENVS.TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS || 5000 +export const TRAINING_AREA_LABELS_FETCH_POOLING_TIME_MS = + ENVS.TRAINING_AREA_LABELS_FETCH_POOLING_INTERVAL_MS || 5000; /** -* The time to poll the backend for the status of the OSM last updated time, in milliseconds (ms). -* Data type: Positive Integer (e.g., 900). -* Default value: 10000 milliseconds (10 seconds). -*/ -export const OSM_LAST_UPDATED_POOLING_INTERVAL_MS = ENVS.OSM_LAST_UPDATED_POOLING_INTERVAL_MS || 10000 \ No newline at end of file + * The time to poll the backend for the status of the OSM last updated time, in milliseconds (ms). + * Data type: Positive Integer (e.g., 900). + * Default value: 10000 milliseconds (10 seconds). + */ +export const OSM_LAST_UPDATED_POOLING_INTERVAL_MS = + ENVS.OSM_LAST_UPDATED_POOLING_INTERVAL_MS || 10000; diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts index 9aebf5f2..74236319 100644 --- a/frontend/src/utils/date-utils.ts +++ b/frontend/src/utils/date-utils.ts @@ -11,6 +11,7 @@ import { DateFilter } from "@/types"; * @returns {string} - The extracted date part in "YYYY-MM-DD" format. */ export const extractDatePart = (isoString: string) => { + if (!isoString) return "N/A"; // Return fallback if undefined return isoString.split("T")[0]; }; From 3026923af906191c1ee079382755d1a818f75685 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Mon, 23 Dec 2024 21:14:14 +0100 Subject: [PATCH 05/33] fix: fixed bug: | --- frontend/src/features/start-mapping/components/header.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index 07045216..c70cf329 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -1,10 +1,9 @@ import { BackButton, ButtonWithIcon } from "@/components/ui/button"; import { Divider } from "@/components/ui/divider"; import { DropDown } from "@/components/ui/dropdown"; -import { ChevronDownIcon, TagsInfoIcon } from "@/components/ui/icons"; +import { ChevronDownIcon } from "@/components/ui/icons"; import { SkeletonWrapper } from "@/components/ui/skeleton"; import { TOAST_NOTIFICATIONS } from "@/constants"; -import { ModelDetailsPopUp } from "@/features/models/components"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { Feature, @@ -19,7 +18,7 @@ import { openInJOSM, showSuccessToast, } from "@/utils"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo } from "react"; import { ModelSettings } from "./model-settings"; import { TQueryParams } from "@/app/routes/start-mapping"; import ModelAction from "./model-action"; From 3dd10ebce5dad7293ff5866de71897d848fb2c05 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Mon, 23 Dec 2024 21:14:47 +0100 Subject: [PATCH 06/33] chore: prettier --- .../components/ui/drawer/mobile-drawer.tsx | 1 - .../start-mapping/components/header.tsx | 14 +-- .../start-mapping/components/index.ts | 2 +- .../components/mobile-drawer.tsx | 86 +++++++++++-------- .../components/model-details-button.tsx | 40 +++++---- frontend/src/utils/date-utils.ts | 2 +- 6 files changed, 84 insertions(+), 61 deletions(-) diff --git a/frontend/src/components/ui/drawer/mobile-drawer.tsx b/frontend/src/components/ui/drawer/mobile-drawer.tsx index f4b5a00d..05d99948 100644 --- a/frontend/src/components/ui/drawer/mobile-drawer.tsx +++ b/frontend/src/components/ui/drawer/mobile-drawer.tsx @@ -36,7 +36,6 @@ export const MobileDrawer = ({ "overflow-hidden": snap !== 1, })} > - {children}
diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index c70cf329..bc86a212 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -46,7 +46,7 @@ const StartMappingHeader = ({ map, popupAnchorId, setShowModelDetails, - showModelDetails + showModelDetails, }: { modelPredictionsExist: boolean; trainingDatasetIsPending: boolean; @@ -61,9 +61,9 @@ const StartMappingHeader = ({ setModelPredictions: React.Dispatch>; map: Map | null; disablePrediction: boolean; - popupAnchorId: string - setShowModelDetails: (x: boolean) => void - showModelDetails: boolean + popupAnchorId: string; + setShowModelDetails: (x: boolean) => void; + showModelDetails: boolean; }) => { const navigate = useNavigate(); @@ -195,7 +195,11 @@ const StartMappingHeader = ({ > {data?.name ?? "N/A"}

- setShowModelDetails(!showModelDetails)} showModelDetails={showModelDetails} popupAnchorId={popupAnchorId} /> + setShowModelDetails(!showModelDetails)} + showModelDetails={showModelDetails} + popupAnchorId={popupAnchorId} + />
diff --git a/frontend/src/features/start-mapping/components/index.ts b/frontend/src/features/start-mapping/components/index.ts index 2a7277e0..8414a027 100644 --- a/frontend/src/features/start-mapping/components/index.ts +++ b/frontend/src/features/start-mapping/components/index.ts @@ -4,4 +4,4 @@ export { default as StartMappingHeader } from "./header"; export { ModelSettings } from "./model-settings"; export { default as ModelAction } from "./model-action"; export { StartMappingMobileDrawer } from "./mobile-drawer"; -export { ModelDetailsButton } from './model-details-button' \ No newline at end of file +export { ModelDetailsButton } from "./model-details-button"; diff --git a/frontend/src/features/start-mapping/components/mobile-drawer.tsx b/frontend/src/features/start-mapping/components/mobile-drawer.tsx index 8634dfed..f24cddf9 100644 --- a/frontend/src/features/start-mapping/components/mobile-drawer.tsx +++ b/frontend/src/features/start-mapping/components/mobile-drawer.tsx @@ -1,42 +1,54 @@ -import { MobileDrawer } from "@/components/ui/drawer" -import { MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION } from "@/utils" -import ModelAction from "./model-action" +import { MobileDrawer } from "@/components/ui/drawer"; +import { MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION } from "@/utils"; +import ModelAction from "./model-action"; import { TModelPredictionsConfig } from "../api/get-model-predictions"; import { TModelPredictions } from "@/types"; import { Map } from "maplibre-gl"; import { ModelDetailsButton } from "./model-details-button"; - -export const StartMappingMobileDrawer = ( - { isOpen, disablePrediction, trainingConfig, setModelPredictions, map, modelPredictions, showModelDetails, setShowModelDetails }: - { - isOpen: boolean, - disablePrediction: boolean, - trainingConfig: TModelPredictionsConfig; - modelPredictions: TModelPredictions; - setModelPredictions: React.Dispatch>; - map: Map | null; - showModelDetails: boolean - setShowModelDetails: (x: boolean) => void - }) => { - - return ( - - {disablePrediction &&

{MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION}

} -
-
- -
-
- setShowModelDetails(!showModelDetails)} showModelDetails={showModelDetails} /> -
-
-
- ) -} \ No newline at end of file +export const StartMappingMobileDrawer = ({ + isOpen, + disablePrediction, + trainingConfig, + setModelPredictions, + map, + modelPredictions, + showModelDetails, + setShowModelDetails, +}: { + isOpen: boolean; + disablePrediction: boolean; + trainingConfig: TModelPredictionsConfig; + modelPredictions: TModelPredictions; + setModelPredictions: React.Dispatch>; + map: Map | null; + showModelDetails: boolean; + setShowModelDetails: (x: boolean) => void; +}) => { + return ( + + {disablePrediction && ( +

+ {MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION} +

+ )} +
+
+ +
+
+ setShowModelDetails(!showModelDetails)} + showModelDetails={showModelDetails} + /> +
+
+
+ ); +}; diff --git a/frontend/src/features/start-mapping/components/model-details-button.tsx b/frontend/src/features/start-mapping/components/model-details-button.tsx index 4dc58962..8d9f3eff 100644 --- a/frontend/src/features/start-mapping/components/model-details-button.tsx +++ b/frontend/src/features/start-mapping/components/model-details-button.tsx @@ -1,17 +1,25 @@ -import { TagsInfoIcon } from "@/components/ui/icons" -import { ToolTip } from "@/components/ui/tooltip" -import { startMappingPageContent } from "@/constants" +import { TagsInfoIcon } from "@/components/ui/icons"; +import { ToolTip } from "@/components/ui/tooltip"; +import { startMappingPageContent } from "@/constants"; -export const ModelDetailsButton = ({ popupAnchorId, onClick, showModelDetails = false }: { popupAnchorId?: string, onClick: () => void, showModelDetails?: boolean }) => { - return ( - - - - ) -} \ No newline at end of file +export const ModelDetailsButton = ({ + popupAnchorId, + onClick, + showModelDetails = false, +}: { + popupAnchorId?: string; + onClick: () => void; + showModelDetails?: boolean; +}) => { + return ( + + + + ); +}; diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts index 74236319..2b205b90 100644 --- a/frontend/src/utils/date-utils.ts +++ b/frontend/src/utils/date-utils.ts @@ -11,7 +11,7 @@ import { DateFilter } from "@/types"; * @returns {string} - The extracted date part in "YYYY-MM-DD" format. */ export const extractDatePart = (isoString: string) => { - if (!isoString) return "N/A"; // Return fallback if undefined + if (!isoString) return "N/A"; // Return fallback if undefined return isoString.split("T")[0]; }; From 006ba0b990dd6716a96fcdc9337777c2d92ce2f3 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Wed, 25 Dec 2024 06:09:24 +0100 Subject: [PATCH 07/33] chore: disabled auth in start mapping page for mobile testing --- frontend/src/app/router.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 92c5efa8..0a67f674 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -47,7 +47,7 @@ const router = createBrowserRouter([ }, }, /** - * Training dataset route starts + * Training dataset route */ { path: APPLICATION_ROUTES.TRAINING_DATASETS, @@ -277,9 +277,9 @@ const router = createBrowserRouter([ ); return { Component: () => ( - - - + // + + // ), }; }, From 160d8988db17b01b448872c00f228b0ca5c70d88 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Wed, 25 Dec 2024 12:23:12 +0100 Subject: [PATCH 08/33] feat: start mapping mobile v2 almost done --- frontend/src/app/router.tsx | 2 +- frontend/src/app/routes/start-mapping.tsx | 291 +++++++++++++++--- frontend/src/assets/svgs/fair_logo.svg | 9 + frontend/src/assets/svgs/index.ts | 1 + .../src/components/layout/navbar/nav-logo.tsx | 16 +- .../components/layout/navbar/user-profile.tsx | 18 +- .../map/controls/current-zoom-control.tsx | 2 +- .../map/controls/fit-to-bounds-control.tsx | 22 +- frontend/src/components/map/controls/index.ts | 2 +- .../components/map/controls/layer-control.tsx | 70 +++-- .../map/controls/legend-control.tsx | 65 ---- frontend/src/components/map/map.tsx | 77 ++--- .../components/map/setups/setup-maplibre.ts | 8 +- frontend/src/components/seo/head.tsx | 2 +- frontend/src/components/ui/dialog/dialog.css | 8 +- frontend/src/components/ui/dialog/dialog.tsx | 1 + .../components/ui/drawer/mobile-drawer.tsx | 41 ++- .../src/components/ui/dropdown/dropdown.css | 5 +- .../src/components/ui/dropdown/dropdown.tsx | 12 +- .../components/ui/form/input/input.module.css | 2 +- .../ui-contents/start-mapping-content.ts | 7 +- .../training-area/training-area-map.tsx | 2 +- .../models/components/model-details-popup.tsx | 243 ++++++++------- .../start-mapping/components/header.tsx | 183 ++--------- .../start-mapping/components/index.ts | 3 +- .../components/logo-with-dropdown.tsx | 45 +++ .../start-mapping/components/map/index.ts | 2 + .../components/map/legend-control.tsx | 95 ++++++ .../components/{ => map}/map.tsx | 103 +++---- .../components/mobile-drawer.tsx | 114 +++++-- .../start-mapping/components/model-action.tsx | 2 +- .../components/model-details-button.tsx | 6 +- .../components/model-predictions-tracker.tsx | 17 + .../components/model-settings.tsx | 58 ++-- .../start-mapping/components/popup.tsx | 12 +- frontend/src/hooks/use-map-instance.ts | 6 +- frontend/src/hooks/use-map-layer.ts | 2 +- frontend/src/hooks/use-screen-size.ts | 9 +- frontend/src/styles/index.css | 29 +- frontend/src/types/ui-contents.ts | 3 +- frontend/src/utils/constants.ts | 2 +- frontend/tailwind.config.js | 1 + 42 files changed, 973 insertions(+), 625 deletions(-) create mode 100644 frontend/src/assets/svgs/fair_logo.svg delete mode 100644 frontend/src/components/map/controls/legend-control.tsx create mode 100644 frontend/src/features/start-mapping/components/logo-with-dropdown.tsx create mode 100644 frontend/src/features/start-mapping/components/map/index.ts create mode 100644 frontend/src/features/start-mapping/components/map/legend-control.tsx rename frontend/src/features/start-mapping/components/{ => map}/map.tsx (79%) create mode 100644 frontend/src/features/start-mapping/components/model-predictions-tracker.tsx diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 0a67f674..24f76504 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -47,7 +47,7 @@ const router = createBrowserRouter([ }, }, /** - * Training dataset route + * Training dataset route */ { path: APPLICATION_ROUTES.TRAINING_DATASETS, diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index b923c92f..ba138485 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -1,26 +1,49 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { Head } from "@/components/seo"; -import { BBOX, TileJSON, TModelPredictions } from "@/types"; +import { BBOX, Feature, TileJSON, TModelPredictions } from "@/types"; import { useModelDetails } from "@/features/models/hooks/use-models"; import { useGetTrainingDataset } from "@/features/models/hooks/use-dataset"; import { + BrandLogoWithDropDown, + Legend, StartMappingHeader, StartMappingMapComponent, StartMappingMobileDrawer, } from "@/features/start-mapping/components"; import { useGetTMSTileJSON } from "@/features/model-creation/hooks/use-tms-tilejson"; import { + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, APPLICATION_ROUTES, extractTileJSONURL, + geoJSONDowloader, MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION, + openInJOSM, PREDICTION_API_FILE_EXTENSIONS, + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + showSuccessToast, } from "@/utils"; import { BASE_MODELS } from "@/enums"; -import { startMappingPageContent } from "@/constants"; +import { startMappingPageContent, TOAST_NOTIFICATIONS } from "@/constants"; import { useMapInstance } from "@/hooks/use-map-instance"; import useScreenSize from "@/hooks/use-screen-size"; import { ModelDetailsPopUp } from "@/features/models/components"; +import { UserProfile } from "@/components/layout"; +import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; +import { FitToBounds, LayerControl, ZoomLevel } from "@/components/map"; +import { LngLatBoundsLike } from "maplibre-gl"; +import { MobileDrawer } from "@/components/ui/drawer"; + +export type TDownloadOptions = { + name: string; + value: string; + onClick: () => void; + showOnMobile: boolean; +}[]; export const SEARCH_PARAMS = { useJOSMQ: "useJOSMQ", @@ -35,14 +58,13 @@ export const StartMappingPage = () => { const { modelId } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const { map, mapContainerRef, currentZoom } = useMapInstance(); - const defaultQueries = { - [SEARCH_PARAMS.useJOSMQ]: searchParams.get(SEARCH_PARAMS.useJOSMQ) || false, - [SEARCH_PARAMS.confidenceLevel]: - searchParams.get(SEARCH_PARAMS.confidenceLevel) || 90, - [SEARCH_PARAMS.tolerance]: searchParams.get(SEARCH_PARAMS.tolerance) || 0.3, - [SEARCH_PARAMS.area]: searchParams.get(SEARCH_PARAMS.area) || 4, - }; - const [query, setQuery] = useState(defaultQueries); + const { isSmallViewport } = useScreenSize(); + const navigate = useNavigate(); + const bounds = map?.getBounds(); + const [showModelDetailsPopup, setShowModelDetailsPopup] = + useState(false); + const { dropdownIsOpened, onDropdownHide, onDropdownShow } = + useDropdownMenu(); const { isError, isPending, data, error } = useModelDetails( modelId as string, @@ -63,53 +85,62 @@ export const StartMappingPage = () => { error: oamTileJSONError, } = useGetTMSTileJSON(tileJSONURL); - const navigate = useNavigate(); - useEffect(() => { if (isError) { navigate(APPLICATION_ROUTES.NOTFOUND, { state: { from: window.location.pathname, - //@ts-expect-error bad type definition + // @ts-expect-error: might not be typed error: error?.response?.data?.detail, }, }); } }, [isError, error, navigate]); + const [query, setQuery] = useState(() => { + return { + [SEARCH_PARAMS.useJOSMQ]: + searchParams.get(SEARCH_PARAMS.useJOSMQ) || false, + [SEARCH_PARAMS.confidenceLevel]: + searchParams.get(SEARCH_PARAMS.confidenceLevel) || 90, + [SEARCH_PARAMS.tolerance]: + searchParams.get(SEARCH_PARAMS.tolerance) || 0.3, + [SEARCH_PARAMS.area]: searchParams.get(SEARCH_PARAMS.area) || 4, + }; + }); + const [modelPredictions, setModelPredictions] = useState({ all: [], accepted: [], rejected: [], }); - const modelPredictionsExist = useMemo( - () => + const modelPredictionsExist = useMemo(() => { + return ( modelPredictions.accepted.length > 0 || modelPredictions.rejected.length > 0 || - modelPredictions.all.length > 0, - [modelPredictions], - ); + modelPredictions.all.length > 0 + ); + }, [modelPredictions]); const updateQuery = useCallback( (newParams: TQueryParams) => { - setQuery((prevQuery) => ({ - ...prevQuery, - ...newParams, - })); + // Merge the new query values + setQuery((prev) => ({ ...prev, ...newParams })); + + // Update the URLSearchParams const updatedParams = new URLSearchParams(searchParams); - Object.entries(newParams).forEach(([key, value]) => { + for (const [key, value] of Object.entries(newParams)) { if (value !== undefined && value !== null) { updatedParams.set(key, String(value)); } else { updatedParams.delete(key); } - }); + } setSearchParams(updatedParams, { replace: true }); }, [searchParams, setSearchParams], ); - const bounds = map?.getBounds(); const trainingConfig = { tolerance: query[SEARCH_PARAMS.tolerance] as number, @@ -130,47 +161,187 @@ export const StartMappingPage = () => { ] as BBOX, }; - const { isMobile } = useScreenSize(); - const disablePrediction = currentZoom < MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION; const popupAnchorId = "model-details"; - const [showModelDetails, setShowModelDetails] = useState(false); + const mapLayers = useMemo( + () => [ + ...(modelPredictions.accepted.length > 0 + ? [ + { + value: + startMappingPageContent.map.controls.legendControl + .acceptedPredictions, + subLayers: [ + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] + : []), + ...(modelPredictions.rejected.length > 0 + ? [ + { + value: + startMappingPageContent.map.controls.legendControl + .rejectedPredictions, + subLayers: [ + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] + : []), + ...(modelPredictions.all.length > 0 + ? [ + { + value: + startMappingPageContent.map.controls.legendControl + .predictionResults, + subLayers: [ + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] + : []), + ], + [modelPredictions, startMappingPageContent], + ); + + const handleAllFeaturesDownload = useCallback(async () => { + geoJSONDowloader( + { + type: "FeatureCollection", + features: [ + ...modelPredictions.accepted, + ...modelPredictions.rejected, + ...modelPredictions.all, + ], + }, + `all_predictions_${data.dataset}`, + ); + showSuccessToast(TOAST_NOTIFICATIONS.startMapping.fileDownloadSuccess); + }, [modelPredictions]); + + const handleAcceptedFeaturesDownload = useCallback(async () => { + geoJSONDowloader( + { type: "FeatureCollection", features: modelPredictions.accepted }, + `accepted_predictions_${data.dataset}`, + ); + showSuccessToast(TOAST_NOTIFICATIONS.startMapping.fileDownloadSuccess); + }, [modelPredictions]); + + const handleFeaturesDownloadToJOSM = useCallback( + (features: Feature[]) => { + if (!map || !oamTileJSON?.name || !trainingDataset?.source_imagery) + return; + openInJOSM( + oamTileJSON.name, + trainingDataset.source_imagery, + features, + true, + ); + }, + [map, oamTileJSON, trainingDataset], + ); + + const handleAllFeaturesDownloadToJOSM = useCallback(() => { + handleFeaturesDownloadToJOSM(modelPredictions.all); + }, [handleFeaturesDownloadToJOSM, modelPredictions.all]); + + const handleAcceptedFeaturesDownloadToJOSM = useCallback(() => { + handleFeaturesDownloadToJOSM(modelPredictions.accepted); + }, [handleFeaturesDownloadToJOSM, modelPredictions.accepted]); + + const downloadOptions: TDownloadOptions = useMemo( + () => [ + { + name: startMappingPageContent.buttons.download.options.allFeatures, + value: startMappingPageContent.buttons.download.options.allFeatures, + onClick: handleAllFeaturesDownload, + showOnMobile: true, + }, + { + name: startMappingPageContent.buttons.download.options.acceptedFeatures, + value: + startMappingPageContent.buttons.download.options.acceptedFeatures, + onClick: handleAcceptedFeaturesDownload, + showOnMobile: true, + }, + { + name: startMappingPageContent.buttons.download.options + .openAllFeaturesInJOSM, + value: + startMappingPageContent.buttons.download.options + .openAllFeaturesInJOSM, + onClick: handleAllFeaturesDownloadToJOSM, + showOnMobile: false, + }, + { + name: startMappingPageContent.buttons.download.options + .openAcceptedFeaturesInJOSM, + value: + startMappingPageContent.buttons.download.options + .openAcceptedFeaturesInJOSM, + onClick: handleAcceptedFeaturesDownloadToJOSM, + showOnMobile: false, + }, + ], + [ + startMappingPageContent, + handleAcceptedFeaturesDownloadToJOSM, + handleAllFeaturesDownloadToJOSM, + handleAcceptedFeaturesDownload, + handleAllFeaturesDownload, + ], + ); + + const handleModelDetailsPopup = useCallback(() => { + setShowModelDetailsPopup((prev) => !prev); + }, [setShowModelDetailsPopup]); return ( <> - setShowModelDetails(false)} - anchor={popupAnchorId} - model={data} - trainingDataset={trainingDataset} - trainingDatasetIsPending={trainingDatasetIsPending} - trainingDatasetIsError={trainingDatasetIsError} - /> + {/* Mobile dialog */}
+ {/* Model Details Popup */} + {data && ( + setShowModelDetailsPopup(false)} + anchor={popupAnchorId} + model={data} + trainingDataset={trainingDataset} + trainingDatasetIsPending={trainingDatasetIsPending} + trainingDatasetIsError={trainingDatasetIsError} + /> + )} + {/* Web Header */} { map={map} disablePrediction={disablePrediction} popupAnchorId={popupAnchorId} - showModelDetails={showModelDetails} - setShowModelDetails={setShowModelDetails} + modelDetailsPopupIsActive={showModelDetailsPopup} + handleModelDetailsPopup={handleModelDetailsPopup} + downloadOptions={downloadOptions} />
-
+
+ {/* Mobile Header and Controls */} +
+
+ +
+
+ +
+
+ + +
+
+ +
+ {map && modelPredictionsExist && } +
+ {/* Map Component */} { mapContainerRef={mapContainerRef} map={map} currentZoom={currentZoom} + layers={mapLayers} + tmsBounds={oamTileJSON?.bounds as LngLatBoundsLike} />
diff --git a/frontend/src/assets/svgs/fair_logo.svg b/frontend/src/assets/svgs/fair_logo.svg new file mode 100644 index 00000000..8fef6c0f --- /dev/null +++ b/frontend/src/assets/svgs/fair_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/svgs/index.ts b/frontend/src/assets/svgs/index.ts index 258cbffc..def76fdd 100644 --- a/frontend/src/assets/svgs/index.ts +++ b/frontend/src/assets/svgs/index.ts @@ -4,3 +4,4 @@ export { default as fAIrValues } from "@/assets/svgs/fair_values.svg"; export { default as HamburgerIcon } from "@/assets/svgs/hamburger_icon.svg"; export { default as JOSMLogo } from "@/assets/svgs/josm_logo.svg"; export { default as OSMLogo } from "@/assets/svgs/osm_logo.svg"; +export { default as BrandLogo } from "./fair_logo.svg"; diff --git a/frontend/src/components/layout/navbar/nav-logo.tsx b/frontend/src/components/layout/navbar/nav-logo.tsx index e7bd3a22..151fb7e3 100644 --- a/frontend/src/components/layout/navbar/nav-logo.tsx +++ b/frontend/src/components/layout/navbar/nav-logo.tsx @@ -1,21 +1,23 @@ import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; import { Image } from "@/components/ui/image"; import { useNavigate } from "react-router-dom"; -import { fAIrLogo } from "@/assets/images"; +import { BrandLogo } from "@/assets/svgs"; export const NavLogo = ({ onClick, - width = "60px", - height = "22px", + smallerSize, }: { onClick?: () => void; - width?: string; - height?: string; + smallerSize?: boolean; }) => { const navigate = useNavigate(); const handleClick = () => { onClick ? onClick() : navigate(APPLICATION_ROUTES.HOMEPAGE); }; + + const width = smallerSize ? "50px" : "60px"; + const height = smallerSize ? "50px" : "22px"; + return ( ); }; diff --git a/frontend/src/components/layout/navbar/user-profile.tsx b/frontend/src/components/layout/navbar/user-profile.tsx index b42dd593..15910d48 100644 --- a/frontend/src/components/layout/navbar/user-profile.tsx +++ b/frontend/src/components/layout/navbar/user-profile.tsx @@ -2,9 +2,10 @@ import styles from "@/components/layout/navbar/navbar.module.css"; import SlAvatar from "@shoelace-style/shoelace/dist/react/avatar/index.js"; import { DropDown } from "@/components/ui/dropdown"; import { useNavigate } from "react-router-dom"; -import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; +import { APP_CONTENT, APPLICATION_ROUTES, truncateString } from "@/utils"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { useAuth } from "@/app/providers/auth-provider"; +import useScreenSize from "@/hooks/use-screen-size"; export const UserProfile = ({ hideFullName, @@ -18,7 +19,8 @@ export const UserProfile = ({ const { onDropdownHide, onDropdownShow, dropdownIsOpened } = useDropdownMenu(); const navigate = useNavigate(); - const size = smallerSize ? "35px" : "40px"; + const { isMobile, isTablet } = useScreenSize(); + const size = smallerSize ? "35px" : isTablet || isMobile ? "28px" : "40px"; return ( {!hideFullName && ( -

{user.username}

+

+ {truncateString(user?.username, 20)} +

)}
} diff --git a/frontend/src/components/map/controls/current-zoom-control.tsx b/frontend/src/components/map/controls/current-zoom-control.tsx index abbe3b13..4afff595 100644 --- a/frontend/src/components/map/controls/current-zoom-control.tsx +++ b/frontend/src/components/map/controls/current-zoom-control.tsx @@ -1,6 +1,6 @@ export const ZoomLevel = ({ currentZoom }: { currentZoom: number }) => { return ( -
+

Zoom level: {currentZoom}

); diff --git a/frontend/src/components/map/controls/fit-to-bounds-control.tsx b/frontend/src/components/map/controls/fit-to-bounds-control.tsx index e528f9dc..4b0300af 100644 --- a/frontend/src/components/map/controls/fit-to-bounds-control.tsx +++ b/frontend/src/components/map/controls/fit-to-bounds-control.tsx @@ -1,13 +1,29 @@ import { ToolTip } from "@/components/ui/tooltip"; import { ArrowMoveIcon } from "@/components/ui/icons"; import { mapContents } from "@/constants"; +import useScreenSize from "@/hooks/use-screen-size"; +import { useCallback } from "react"; +import { Map } from "maplibre-gl"; + +export const FitToBounds = ({ + map, + bounds, +}: { + map: Map | null; + bounds: any; +}) => { + const { isSmallViewport } = useScreenSize(); + + const fitToBounds = useCallback(() => { + if (!map || !bounds) return; + map?.fitBounds(bounds); + }, [map, bounds]); -export const FitToBounds = ({ onClick }: { onClick: () => void }) => { return ( diff --git a/frontend/src/components/map/controls/index.ts b/frontend/src/components/map/controls/index.ts index 750d8444..01f1409a 100644 --- a/frontend/src/components/map/controls/index.ts +++ b/frontend/src/components/map/controls/index.ts @@ -3,5 +3,5 @@ export { DrawControl } from "./draw-control"; export { ZoomLevel } from "./current-zoom-control"; export { LayerControl } from "./layer-control"; export { MapCursorToolTip } from "./map-cursor-tooltip"; -export { Legend } from "./legend-control"; export { FitToBounds } from "./fit-to-bounds-control"; +export { GeolocationControl } from "./geolocation-control"; diff --git a/frontend/src/components/map/controls/layer-control.tsx b/frontend/src/components/map/controls/layer-control.tsx index 283419f7..bcdc2dea 100644 --- a/frontend/src/components/map/controls/layer-control.tsx +++ b/frontend/src/components/map/controls/layer-control.tsx @@ -2,10 +2,16 @@ import { LayerStackIcon } from "@/components/ui/icons"; import { DropDown } from "@/components/ui/dropdown"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { Map } from "maplibre-gl"; -import { useEffect, useState } from "react"; -import { CheckboxGroup } from "../../ui/form"; -import { ToolTip } from "../../ui/tooltip"; +import { useEffect, useMemo, useState } from "react"; +import { CheckboxGroup } from "@/components/ui/form"; +import { ToolTip } from "@/components/ui/tooltip"; import { BASEMAPS, ToolTipPlacement } from "@/enums"; +import { + GOOGLE_SATELLITE_BASEMAP_LAYER_ID, + OSM_BASEMAP_LAYER_ID, + TMS_LAYER_ID, +} from "@/utils"; +import useScreenSize from "@/hooks/use-screen-size"; type TLayers = { id?: string; subLayers: string[]; value: string }[]; type TBasemaps = { id?: string; subLayer: string; value: string }[]; @@ -13,14 +19,36 @@ type TBasemaps = { id?: string; subLayer: string; value: string }[]; export const LayerControl = ({ map, layers, - basemaps, + basemaps = true, + openAerialMap = false, }: { map: Map | null; layers: TLayers; - basemaps: TBasemaps; + basemaps?: boolean; + openAerialMap?: boolean; }) => { const { dropdownIsOpened, onDropdownHide, onDropdownShow } = useDropdownMenu(); + const { isTablet, isMobile } = useScreenSize(); + + const layerControlData = useMemo(() => { + const layers_ = [ + ...layers, + ...(openAerialMap + ? [{ value: "TMS Layer", subLayers: [TMS_LAYER_ID] }] + : []), + ]; + const baseLayers: TBasemaps = basemaps + ? [ + { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, + { + value: BASEMAPS.GOOGLE_SATELLITE, + subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, + }, + ] + : []; + return { layers_, baseLayers }; + }, [layers, openAerialMap, basemaps]); const [layerVisibility, setLayerVisibility] = useState<{ [key: string]: boolean; @@ -30,7 +58,7 @@ export const LayerControl = ({ }>({}); useEffect(() => { - const initialVisibility = layers.reduce( + const initialVisibility = layerControlData.layers_.reduce( (acc, { value }) => { acc[value] = layerVisibility[value] !== undefined ? layerVisibility[value] : true; @@ -40,10 +68,10 @@ export const LayerControl = ({ ); setLayerVisibility(initialVisibility); - }, [layers]); + }, [layerControlData.layers_]); useEffect(() => { - const initialVisibility = basemaps.reduce( + const initialVisibility = layerControlData.baseLayers.reduce( (acc, { value }) => { acc[value] = basemapVisibility[value] !== undefined @@ -55,12 +83,12 @@ export const LayerControl = ({ ); setBasemapVisibility(initialVisibility); - }, [basemaps]); + }, [layerControlData.baseLayers]); const handleLayerSelection = (newSelectedLayers: string[]) => { const updatedVisibility = { ...layerVisibility }; - layers.forEach(({ value, subLayers }) => { + layerControlData.layers_.forEach(({ value, subLayers }) => { updatedVisibility[value] = newSelectedLayers.includes(value); if (map?.isStyleLoaded) { @@ -83,7 +111,7 @@ export const LayerControl = ({ const handleBasemapSelection = (newSelectedBasemap: string[]) => { const updatedVisibility = { ...basemapVisibility }; - basemaps.forEach(({ value, subLayer }) => { + layerControlData.baseLayers.forEach(({ value, subLayer }) => { updatedVisibility[value] = newSelectedBasemap.includes(value); if (map?.isStyleLoaded) { if (map.getLayer(subLayer)) { @@ -107,26 +135,30 @@ export const LayerControl = ({ onDropdownShow={onDropdownShow} disableCheveronIcon triggerComponent={ -
+
} withCheckbox distance={10} > -
- {basemaps.length > 0 && ( +
+ {layerControlData.baseLayers.length > 0 ? ( <>

Basemap

- )} - {layers.length > 0 && ( + ) : null} + {layerControlData.layers_.length > 0 ? ( <>

Layers

layerVisibility[layer], )} multiple - options={layers} + options={layerControlData.layers_} // @ts-expect-error bad type definition onCheck={handleLayerSelection} /> - )} + ) : null}
diff --git a/frontend/src/components/map/controls/legend-control.tsx b/frontend/src/components/map/controls/legend-control.tsx deleted file mode 100644 index eb9e5cab..00000000 --- a/frontend/src/components/map/controls/legend-control.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useState } from "react"; -import { LegendBookIcon } from "../../ui/icons"; -import { LEGEND_NAME_MAPPING, MAP_STYLES_PREFIX } from "@/utils"; -import { Map } from "maplibre-gl"; - -const FillLegendStyle = ({ - fillColor, - fillOpacity, -}: { - fillColor: string; - fillOpacity: number; -}) => { - return ( - - ); -}; - -export const Legend = ({ map }: { map: Map | null }) => { - const [expandLegend, setExpandLegend] = useState(true); - - const activeLayers = map - ?.getStyle() - .layers?.filter( - (layer) => - layer.id.includes(MAP_STYLES_PREFIX) && - layer.layout?.visibility === "visible", - ); - - return ( - - ); -}; diff --git a/frontend/src/components/map/map.tsx b/frontend/src/components/map/map.tsx index abe2f1b1..bf368084 100644 --- a/frontend/src/components/map/map.tsx +++ b/frontend/src/components/map/map.tsx @@ -1,24 +1,19 @@ -import { - GOOGLE_SATELLITE_BASEMAP_LAYER_ID, - OSM_BASEMAP_LAYER_ID, - TMS_LAYER_ID, -} from "@/utils"; import "maplibre-gl/dist/maplibre-gl.css"; import { RefObject, useMemo } from "react"; -import { BASEMAPS, DrawingModes } from "@/enums"; - -import { ZoomControls } from "@/components/map/controls/zoom-control"; -import { GeolocationControl } from "@/components/map/controls/geolocation-control"; -import { DrawControl } from "@/components/map/controls/draw-control"; -import { ZoomLevel } from "@/components/map/controls/current-zoom-control"; -import { LayerControl } from "@/components/map/controls/layer-control"; -import { Legend } from "@/components/map/controls/legend-control"; +import { DrawingModes } from "@/enums"; +import { + GeolocationControl, + FitToBounds, + DrawControl, + ZoomLevel, + LayerControl, + ZoomControls, +} from "@/components/map/controls"; import { TileBoundaries } from "@/components/map/layers/tile-boundaries"; import { OpenAerialMap } from "@/components/map/layers/open-aerial-map"; import { Basemaps } from "@/components/map/layers/basemaps"; import { ControlsPosition } from "@/enums"; import { LngLatBoundsLike, Map } from "maplibre-gl"; -import { FitToBounds } from "./controls"; import { TerraDraw } from "terra-draw"; type MapComponentProps = { @@ -31,9 +26,8 @@ type MapComponentProps = { value: string; subLayers: string[]; }[]; - showTileBoundary?: boolean; + showTileBoundaries?: boolean; children?: React.ReactNode; - showLegend?: boolean; openAerialMap?: boolean; oamTileJSONURL?: string; basemaps?: boolean; @@ -48,6 +42,7 @@ type MapComponentProps = { currentZoom?: number; drawingMode?: DrawingModes; setDrawingMode?: (newMode: DrawingModes) => void; + zoomControls?: boolean; }; export const MapComponent: React.FC = ({ @@ -57,8 +52,7 @@ export const MapComponent: React.FC = ({ showCurrentZoom = false, layerControl = false, layerControlLayers = [], - showTileBoundary = false, - showLegend = false, + showTileBoundaries = false, openAerialMap = false, oamTileJSONURL, basemaps = false, @@ -70,27 +64,9 @@ export const MapComponent: React.FC = ({ terraDraw, currentZoom, drawingMode, + zoomControls = true, setDrawingMode, }) => { - const layerControlData = useMemo(() => { - const layers = [ - ...layerControlLayers, - ...(openAerialMap - ? [{ value: "TMS Layer", subLayers: [TMS_LAYER_ID] }] - : []), - ]; - const baseLayers = basemaps - ? [ - { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, - { - value: BASEMAPS.GOOGLE_SATELLITE, - subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, - }, - ] - : []; - return { layers, baseLayers }; - }, [layerControlLayers, openAerialMap, basemaps]); - const Controls = useMemo(() => { if (!map) return; return ( @@ -100,18 +76,11 @@ export const MapComponent: React.FC = ({ controlsPosition === ControlsPosition.TOP_RIGHT ? "right-3" : "left-3" - } z-[1] flex flex-col gap-y-[1px]`} + } map-elements-z-index flex flex-col gap-y-[1px]`} > - {currentZoom ? ( + {currentZoom && zoomControls ? ( ) : null} - {fitToBounds ? ( - - map?.fitBounds(bounds as LngLatBoundsLike, { padding: 10 }) - } - /> - ) : null} {geolocationControl && } {drawControl && terraDraw && drawingMode && setDrawingMode && ( = ({ /> )}
+ {map && fitToBounds && ( +
+ +
+ )}
{showCurrentZoom && currentZoom ? ( ) : null} {layerControl && ( )}
@@ -145,7 +120,6 @@ export const MapComponent: React.FC = ({ controlsPosition, layerControl, showCurrentZoom, - layerControlData, currentZoom, drawingMode && setDrawingMode, fitToBounds, @@ -154,13 +128,12 @@ export const MapComponent: React.FC = ({ return (
{Controls} - {map && showLegend && } {/* Order according to how they'll be rendered */} {basemaps && } {openAerialMap && oamTileJSONURL && ( )} - {showTileBoundary && } + {showTileBoundaries && } {children}
); diff --git a/frontend/src/components/map/setups/setup-maplibre.ts b/frontend/src/components/map/setups/setup-maplibre.ts index bfaffbe9..3bc19304 100644 --- a/frontend/src/components/map/setups/setup-maplibre.ts +++ b/frontend/src/components/map/setups/setup-maplibre.ts @@ -1,10 +1,10 @@ -import { MAX_ZOOM_LEVEL } from "@/utils"; -import maplibregl, { Map, StyleSpecification } from "maplibre-gl"; +import { BASEMAPS } from "@/enums"; +import { MAP_STYLES, MAX_ZOOM_LEVEL } from "@/utils"; +import maplibregl, { Map } from "maplibre-gl"; import { Protocol } from "pmtiles"; export const setupMaplibreMap = ( containerRef: React.RefObject, - style: StyleSpecification | string, pmtiles: boolean, ): Map => { // Check if RTL plugin is needed and set it @@ -22,7 +22,7 @@ export const setupMaplibreMap = ( const map = new maplibregl.Map({ container: containerRef.current!, - style: style, + style: MAP_STYLES[BASEMAPS.OSM], center: [0, 0], zoom: 0.5, minZoom: 1, diff --git a/frontend/src/components/seo/head.tsx b/frontend/src/components/seo/head.tsx index 7dd0e234..a0b12a45 100644 --- a/frontend/src/components/seo/head.tsx +++ b/frontend/src/components/seo/head.tsx @@ -10,7 +10,7 @@ export const Head = ({ title = "", description = "" }: HeadProps = {}) => { {title - ? `${title} | fAIr | Humanitarian OpenStreetMap Team (HOT)` + ? `${title} | fAIr - Humanitarian OpenStreetMap Team (HOT)` : undefined} diff --git a/frontend/src/components/ui/dialog/dialog.css b/frontend/src/components/ui/dialog/dialog.css index 80bd3743..899f4af4 100644 --- a/frontend/src/components/ui/dialog/dialog.css +++ b/frontend/src/components/ui/dialog/dialog.css @@ -1,8 +1,14 @@ sl-dialog::part(title) { - font-size: var(--hot-fair-font-size-title-3); + font-size: var(--hot-fair-font-size-title-4); font-weight: var(--hot-fair-font-weight-semibold); } sl-dialog.primary::part(title) { color: var(--hot-fair-color-primary); } + +@media (min-width: 768px) { + sl-dialog::part(title) { + font-size: var(--hot-fair-font-size-title-3); + } +} diff --git a/frontend/src/components/ui/dialog/dialog.tsx b/frontend/src/components/ui/dialog/dialog.tsx index f3b91c44..fc979025 100644 --- a/frontend/src/components/ui/dialog/dialog.tsx +++ b/frontend/src/components/ui/dialog/dialog.tsx @@ -25,6 +25,7 @@ const Dialog: React.FC = ({ event.preventDefault(); } } + const { isMobile, isTablet, isLaptop } = useScreenSize(); const size = diff --git a/frontend/src/components/ui/drawer/mobile-drawer.tsx b/frontend/src/components/ui/drawer/mobile-drawer.tsx index 05d99948..53c21158 100644 --- a/frontend/src/components/ui/drawer/mobile-drawer.tsx +++ b/frontend/src/components/ui/drawer/mobile-drawer.tsx @@ -2,15 +2,23 @@ import { cn } from "@/utils"; import React, { useState } from "react"; import { Drawer } from "vaul"; -const snapPoints = ["150px", "355px", 1]; - export const MobileDrawer = ({ open, children, + dialogTitle, + canClose = false, + closeDrawer, + startingSnapPoint = "150px", }: { open: boolean; children: React.ReactNode; + dialogTitle: string; + closeDrawer?: () => void; + canClose?: boolean; + startingSnapPoint?: number | string; }) => { + const snapPoints = [startingSnapPoint, "355px", 0.8]; + const [snap, setSnap] = useState(snapPoints[0]); return ( @@ -19,22 +27,41 @@ export const MobileDrawer = ({ activeSnapPoint={snap} setActiveSnapPoint={setSnap} open={open} + onClose={closeDrawer} > + {canClose ? ( + + ✕ + + ) : null} + +
{children}
diff --git a/frontend/src/components/ui/dropdown/dropdown.css b/frontend/src/components/ui/dropdown/dropdown.css index 1af51f4a..650f6bc2 100644 --- a/frontend/src/components/ui/dropdown/dropdown.css +++ b/frontend/src/components/ui/dropdown/dropdown.css @@ -6,6 +6,7 @@ sl-menu-item::part(base) { color: var(--hot-fair-color-dark); background-color: var(--hot-fair-color-white); + font-size: var(--hot-fair-font-size-body-text-3); } sl-menu-item::part(base):hover { @@ -16,10 +17,6 @@ sl-menu-item.logoutButton::part(base) { color: var(--hot-fair-color-primary); } -sl-menu-item.small::part(base) { - font-size: var(--hot-fair-font-size-body-text-3); -} - /* Checkbox customization */ sl-checkbox::part(control) { diff --git a/frontend/src/components/ui/dropdown/dropdown.tsx b/frontend/src/components/ui/dropdown/dropdown.tsx index df635e97..0a033ee9 100644 --- a/frontend/src/components/ui/dropdown/dropdown.tsx +++ b/frontend/src/components/ui/dropdown/dropdown.tsx @@ -29,7 +29,7 @@ type DropDownProps = { withCheckbox?: boolean; defaultSelectedItems?: string[]; defaultSelectedItem?: string; - menuItemTextSize?: "default" | "small"; + multiSelect?: boolean; triggerComponent: React.ReactNode; distance?: number; @@ -50,7 +50,7 @@ const DropDown: React.FC = ({ defaultSelectedItems = [], defaultSelectedItem = "", multiSelect = false, - menuItemTextSize = "default", + triggerComponent, distance = 20, disableCheveronIcon = false, @@ -121,14 +121,18 @@ const DropDown: React.FC = ({ /> )}
-
+
{menuItems && menuItems.length > 0 ? ( {menuItems?.map((menuItem, id) => ( diff --git a/frontend/src/components/ui/form/input/input.module.css b/frontend/src/components/ui/form/input/input.module.css index 8a3fcd7e..678a2e9d 100644 --- a/frontend/src/components/ui/form/input/input.module.css +++ b/frontend/src/components/ui/form/input/input.module.css @@ -21,7 +21,7 @@ sl-input.customInput::part(form-control-input):focus { sl-input.customInput::part(input)::placeholder { color: var(--hot-fair-color-gray); - font-size: var(--hot-fair-font-size-body-text-2base); + font-size: var(--hot-fair-font-size-body-text-3); } sl-input::part(input) { diff --git a/frontend/src/constants/ui-contents/start-mapping-content.ts b/frontend/src/constants/ui-contents/start-mapping-content.ts index adb46430..04f8e22b 100644 --- a/frontend/src/constants/ui-contents/start-mapping-content.ts +++ b/frontend/src/constants/ui-contents/start-mapping-content.ts @@ -9,6 +9,7 @@ export const startMappingPageContent: TStartMappingPageContent = { }, legendControl: { title: "Legend", + tooltip: "Legend", acceptedPredictions: "Accepted Predictions", rejectedPredictions: "Rejected Predictions", predictionResults: "Prediction Results", @@ -41,8 +42,8 @@ export const startMappingPageContent: TStartMappingPageContent = { download: { label: "Actions", options: { - allFeatures: "Download all Features as GeoJSON", - acceptedFeatures: "Download accepted Features as GeoJSON", + allFeatures: "All features as GeoJSON", + acceptedFeatures: "Accepted features as GeoJSON", openAllFeaturesInJOSM: "Open all Features to JOSM", openAcceptedFeaturesInJOSM: "Open accepted Features to JOSM", }, @@ -74,7 +75,7 @@ export const startMappingPageContent: TStartMappingPageContent = { rejected: "Rejected", }, actions: { - disabledModeTooltip: "Run prediction to see actions", + disabledModeTooltip: (param: string) => `Run prediction to ${param}`, }, modelDetails: { error: "Error retrieving model information.", diff --git a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx index 2ad8613f..3ec635e7 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx @@ -289,7 +289,7 @@ const TrainingAreaMap = ({ drawControl showCurrentZoom layerControl - showTileBoundary + showTileBoundaries basemaps map={map} terraDraw={terraDraw} diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index dd5d39e1..b74efb5e 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -1,29 +1,34 @@ import { Popup } from "@/components/ui/popup"; -import { useModelDetails } from "../hooks/use-models"; +import { useModelDetails } from "@/features/models/hooks/use-models"; import { SkeletonWrapper } from "@/components/ui/skeleton"; import { extractDatePart, roundNumber, truncateString } from "@/utils"; import { TModelDetails, TTrainingDataset } from "@/types"; import { useTrainingDetails } from "@/features/models/hooks/use-training"; import { startMappingPageContent } from "@/constants"; +import useScreenSize from "@/hooks/use-screen-size"; +import { useMemo } from "react"; +import { MobileDrawer } from "@/components/ui/drawer"; const ModelDetailsPopUp = ({ showPopup, anchor, - closePopup, + handlePopup, modelId, model, trainingDatasetIsError, trainingDataset, trainingDatasetIsPending, + closeMobileDrawer, }: { showPopup: boolean; anchor: string; - closePopup: () => void; + handlePopup: () => void; modelId?: string; model?: TModelDetails; trainingDataset?: TTrainingDataset; trainingDatasetIsPending?: boolean; trainingDatasetIsError?: boolean; + closeMobileDrawer: () => void; }) => { const { data, isPending, isError } = useModelDetails( modelId as string, @@ -37,119 +42,145 @@ const ModelDetailsPopUp = ({ } = useTrainingDetails( model?.published_training ?? (data?.published_training as number), ); + const { isSmallViewport } = useScreenSize(); + + const popupContent = useMemo(() => { + return ( + +
+

+ {" "} + {startMappingPageContent.modelDetails.popover.modelId}:{" "} + {model?.id ?? data?.id} +

+

+ {startMappingPageContent.modelDetails.popover.description}:{" "} + + {model?.description ?? data?.description} + +

+

+ {startMappingPageContent.modelDetails.popover.lastModified}:{" "} + + {extractDatePart( + model?.last_modified ?? (data?.last_modified as string), + )} + +

+

+ {startMappingPageContent.modelDetails.popover.trainingId}:{" "} + + {model?.published_training ?? data?.published_training} + +

+

+ {startMappingPageContent.modelDetails.popover.datasetId}:{" "} + + {model?.dataset ?? data?.dataset} + +

+

+ {startMappingPageContent.modelDetails.popover.datasetName}:{" "} + + + {trainingDatasetIsError + ? "N/A" + : truncateString(trainingDataset?.name, 40)}{" "} + + +

+ +

+ {startMappingPageContent.modelDetails.popover.zoomLevel}:{" "} + + + {trainingDetailsError + ? "N/A" + : trainingDetails?.zoom_level?.join(", ")}{" "} + + +

+ +

+ {startMappingPageContent.modelDetails.popover.accuracy}:{" "} + + {roundNumber(model?.accuracy ?? (data?.accuracy as number), 2)}% + +

+

+ {startMappingPageContent.modelDetails.popover.baseModel}:{" "} + + {model?.base_model ?? data?.base_model} + +

+
+
+ ); + }, [modelId, isPending, model, data, isError]); + + if (isSmallViewport) { + return ( + +
+ {!model && isError ? ( +
{startMappingPageContent.modelDetails.error}
+ ) : ( +
+ {popupContent} +
+ )} +
+
+ ); + } return ( - { - -
- {!model && isError ? ( -
{startMappingPageContent.modelDetails.error}
- ) : ( -
-
- -

- {startMappingPageContent.modelDetails.label} -

-
-
-

- {" "} - {startMappingPageContent.modelDetails.popover.modelId}:{" "} - {model?.id ?? data?.id} -

-

- {startMappingPageContent.modelDetails.popover.description}:{" "} - - {model?.description ?? data?.description} - -

-

- {startMappingPageContent.modelDetails.popover.lastModified}:{" "} - - {extractDatePart( - model?.last_modified ?? (data?.last_modified as string), - )} - -

-

- {startMappingPageContent.modelDetails.popover.trainingId}:{" "} - - {model?.published_training ?? data?.published_training} - -

-

- {startMappingPageContent.modelDetails.popover.datasetId}:{" "} - - {model?.dataset ?? data?.dataset} - -

-

- {startMappingPageContent.modelDetails.popover.datasetName}:{" "} - - - {trainingDatasetIsError - ? "N/A" - : truncateString(trainingDataset?.name, 40)}{" "} - - -

- -

- {startMappingPageContent.modelDetails.popover.zoomLevel}:{" "} - - - {trainingDetailsError - ? "N/A" - : trainingDetails?.zoom_level?.join(", ")}{" "} - - -

- -

- {startMappingPageContent.modelDetails.popover.accuracy}:{" "} - - {roundNumber( - model?.accuracy ?? (data?.accuracy as number), - 2, - )} - % - -

-

- {startMappingPageContent.modelDetails.popover.baseModel}:{" "} - - {model?.base_model ?? data?.base_model} - -

-
+
+ {!model && isError ? ( +
{startMappingPageContent.modelDetails.error}
+ ) : ( +
+
+
+ +

+ {startMappingPageContent.modelDetails.label} +

- )} +
+ {popupContent}
- - } + )} +
); }; diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index bc86a212..462ab2e4 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -1,41 +1,27 @@ -import { BackButton, ButtonWithIcon } from "@/components/ui/button"; -import { Divider } from "@/components/ui/divider"; +import { ButtonWithIcon } from "@/components/ui/button"; import { DropDown } from "@/components/ui/dropdown"; import { ChevronDownIcon } from "@/components/ui/icons"; import { SkeletonWrapper } from "@/components/ui/skeleton"; -import { TOAST_NOTIFICATIONS } from "@/constants"; + import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; -import { - Feature, - TileJSON, - TModel, - TModelPredictions, - TTrainingDataset, -} from "@/types"; -import { - APPLICATION_ROUTES, - geoJSONDowloader, - openInJOSM, - showSuccessToast, -} from "@/utils"; -import { useCallback, useMemo } from "react"; -import { ModelSettings } from "./model-settings"; -import { TQueryParams } from "@/app/routes/start-mapping"; -import ModelAction from "./model-action"; -import { TModelPredictionsConfig } from "../api/get-model-predictions"; +import { TModel, TModelPredictions } from "@/types"; +import { ModelSettings } from "@/features/start-mapping/components/model-settings"; +import { TDownloadOptions, TQueryParams } from "@/app/routes/start-mapping"; +import ModelAction from "@/features/start-mapping/components/model-action"; +import { TModelPredictionsConfig } from "@/features/start-mapping/api/get-model-predictions"; import { SHOELACE_SIZES } from "@/enums"; -import { NavLogo, UserProfile } from "@/components/layout"; -import { useNavigate } from "react-router-dom"; +import { UserProfile } from "@/components/layout"; import { startMappingPageContent } from "@/constants"; import { Map } from "maplibre-gl"; import { ToolTip } from "@/components/ui/tooltip"; -import { ModelDetailsButton } from "./model-details-button"; +import { ModelDetailsButton } from "@/features/start-mapping/components/model-details-button"; +import { BrandLogoWithDropDown } from "./logo-with-dropdown"; +import { ModelPredictionsTracker } from "@/features/start-mapping/components/model-predictions-tracker"; +import { MobileDrawer } from "@/components/ui/drawer"; const StartMappingHeader = ({ data, modelPredictions, - oamTileJSON, - trainingDataset, modelPredictionsExist, trainingDatasetIsPending, query, @@ -45,16 +31,15 @@ const StartMappingHeader = ({ disablePrediction, map, popupAnchorId, - setShowModelDetails, - showModelDetails, + handleModelDetailsPopup, + modelDetailsPopupIsActive, + downloadOptions, }: { modelPredictionsExist: boolean; trainingDatasetIsPending: boolean; trainingDatasetIsError: boolean; data: TModel; modelPredictions: TModelPredictions; - trainingDataset?: TTrainingDataset; - oamTileJSON?: TileJSON; query: TQueryParams; updateQuery: (newParams: TQueryParams) => void; trainingConfig: TModelPredictionsConfig; @@ -62,11 +47,10 @@ const StartMappingHeader = ({ map: Map | null; disablePrediction: boolean; popupAnchorId: string; - setShowModelDetails: (x: boolean) => void; - showModelDetails: boolean; + handleModelDetailsPopup: () => void; + modelDetailsPopupIsActive: boolean; + downloadOptions: TDownloadOptions; }) => { - const navigate = useNavigate(); - const { onDropdownHide, onDropdownShow, dropdownIsOpened } = useDropdownMenu(); @@ -76,118 +60,15 @@ const StartMappingHeader = ({ dropdownIsOpened: FAIRLogoDropdownIsOpened, } = useDropdownMenu(); - const handleAllFeaturesDownload = useCallback(async () => { - geoJSONDowloader( - { - type: "FeatureCollection", - features: [ - ...modelPredictions.accepted, - ...modelPredictions.rejected, - ...modelPredictions.all, - ], - }, - `all_predictions_${data.dataset}`, - ); - showSuccessToast(TOAST_NOTIFICATIONS.startMapping.fileDownloadSuccess); - }, [modelPredictions]); - - const handleAcceptedFeaturesDownload = useCallback(async () => { - geoJSONDowloader( - { type: "FeatureCollection", features: modelPredictions.accepted }, - `accepted_predictions_${data.dataset}`, - ); - showSuccessToast(TOAST_NOTIFICATIONS.startMapping.fileDownloadSuccess); - }, [modelPredictions]); - - const handleFeaturesDownloadToJOSM = useCallback( - (features: Feature[]) => { - if (!map || !oamTileJSON?.name || !trainingDataset?.source_imagery) - return; - openInJOSM( - oamTileJSON.name, - trainingDataset.source_imagery, - features, - true, - ); - }, - [map, oamTileJSON, trainingDataset], - ); - - const handleAllFeaturesDownloadToJOSM = useCallback(() => { - handleFeaturesDownloadToJOSM(modelPredictions.all); - }, [handleFeaturesDownloadToJOSM, modelPredictions.all]); - - const handleAcceptedFeaturesDownloadToJOSM = useCallback(() => { - handleFeaturesDownloadToJOSM(modelPredictions.accepted); - }, [handleFeaturesDownloadToJOSM, modelPredictions.accepted]); - - const downloadButtonDropdownOptions = useMemo( - () => [ - { - name: startMappingPageContent.buttons.download.options.allFeatures, - value: startMappingPageContent.buttons.download.options.allFeatures, - onClick: handleAllFeaturesDownload, - }, - { - name: startMappingPageContent.buttons.download.options.acceptedFeatures, - value: - startMappingPageContent.buttons.download.options.acceptedFeatures, - onClick: handleAcceptedFeaturesDownload, - }, - { - name: startMappingPageContent.buttons.download.options - .openAllFeaturesInJOSM, - value: - startMappingPageContent.buttons.download.options - .openAllFeaturesInJOSM, - onClick: handleAllFeaturesDownloadToJOSM, - }, - { - name: startMappingPageContent.buttons.download.options - .openAcceptedFeaturesInJOSM, - value: - startMappingPageContent.buttons.download.options - .openAcceptedFeaturesInJOSM, - onClick: handleAcceptedFeaturesDownloadToJOSM, - }, - ], - [ - startMappingPageContent, - handleAcceptedFeaturesDownloadToJOSM, - handleAllFeaturesDownloadToJOSM, - handleAcceptedFeaturesDownload, - handleAllFeaturesDownload, - ], - ); - return (
- null} width="45px" />} - > -
- - - - -
-
+

{data?.name ?? "N/A"}

+ setShowModelDetails(!showModelDetails)} - showModelDetails={showModelDetails} + onClick={handleModelDetailsPopup} + modelDetailsPopupIsActive={modelDetailsPopupIsActive} popupAnchorId={popupAnchorId} />
@@ -205,24 +87,21 @@ const StartMappingHeader = ({
-

- {startMappingPageContent.mapData.accepted}:{" "} - {modelPredictions.accepted.length}{" "} - {startMappingPageContent.mapData.rejected}:{" "} - {modelPredictions.rejected.length}{" "} -

+ diff --git a/frontend/src/features/start-mapping/components/index.ts b/frontend/src/features/start-mapping/components/index.ts index 8414a027..f21d7d31 100644 --- a/frontend/src/features/start-mapping/components/index.ts +++ b/frontend/src/features/start-mapping/components/index.ts @@ -1,7 +1,8 @@ -export { default as StartMappingMapComponent } from "./map"; +export * from "./map"; export { default as PredictedFeatureActionPopup } from "./popup"; export { default as StartMappingHeader } from "./header"; export { ModelSettings } from "./model-settings"; export { default as ModelAction } from "./model-action"; export { StartMappingMobileDrawer } from "./mobile-drawer"; export { ModelDetailsButton } from "./model-details-button"; +export { BrandLogoWithDropDown } from "./logo-with-dropdown"; diff --git a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx new file mode 100644 index 00000000..8dfd17a9 --- /dev/null +++ b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx @@ -0,0 +1,45 @@ +import { NavLogo } from "@/components/layout"; +import { BackButton } from "@/components/ui/button"; +import { Divider } from "@/components/ui/divider"; +import { DropDown } from "@/components/ui/dropdown"; +import { APPLICATION_ROUTES } from "@/utils"; +import { useNavigate } from "react-router-dom"; + +export const BrandLogoWithDropDown = ({ + isOpened, + onClose, + onShow, +}: { + isOpened: boolean; + onClose: () => void; + onShow: () => void; +}) => { + const navigate = useNavigate(); + return ( + null} smallerSize />} + distance={1} + > +
+ + + + +
+
+ ); +}; diff --git a/frontend/src/features/start-mapping/components/map/index.ts b/frontend/src/features/start-mapping/components/map/index.ts new file mode 100644 index 00000000..39c4b1f0 --- /dev/null +++ b/frontend/src/features/start-mapping/components/map/index.ts @@ -0,0 +1,2 @@ +export { Legend } from "./legend-control"; +export { StartMappingMapComponent } from "./map"; diff --git a/frontend/src/features/start-mapping/components/map/legend-control.tsx b/frontend/src/features/start-mapping/components/map/legend-control.tsx new file mode 100644 index 00000000..bcf3ba08 --- /dev/null +++ b/frontend/src/features/start-mapping/components/map/legend-control.tsx @@ -0,0 +1,95 @@ +import { useCallback, useMemo, useState } from "react"; +import { LegendBookIcon } from "@/components/ui/icons"; +import { LEGEND_NAME_MAPPING, MAP_STYLES_PREFIX } from "@/utils"; +import { Map } from "maplibre-gl"; +import useScreenSize from "@/hooks/use-screen-size"; +import { ToolTip } from "@/components/ui/tooltip"; +import { startMappingPageContent } from "@/constants"; + +const FillLegendStyle = ({ + fillColor, + fillOpacity, +}: { + fillColor: string; + fillOpacity: number; +}) => { + return ( + + ); +}; + +export const Legend = ({ map }: { map: Map | null }) => { + const [expandLegend, setExpandLegend] = useState(true); + + const activeFillLayers = useMemo(() => { + if (!map?.getStyle()?.layers) return []; + return ( + map + .getStyle() + .layers?.filter( + (layer) => + layer.id.includes(MAP_STYLES_PREFIX) && + layer.layout?.visibility === "visible" && + layer.type === "fill", + ) + .reverse() || [] + ); + }, [map]); + + const handleToggleExpand = useCallback(() => { + setExpandLegend((prev) => !prev); + }, []); + + const { isSmallViewport } = useScreenSize(); + + return ( + + + + ); +}; diff --git a/frontend/src/features/start-mapping/components/map.tsx b/frontend/src/features/start-mapping/components/map/map.tsx similarity index 79% rename from frontend/src/features/start-mapping/components/map.tsx rename to frontend/src/features/start-mapping/components/map/map.tsx index 1ee23b05..1a2decaf 100644 --- a/frontend/src/features/start-mapping/components/map.tsx +++ b/frontend/src/features/start-mapping/components/map/map.tsx @@ -8,7 +8,7 @@ import { useState, } from "react"; -import { GeoJSONSource, Map } from "maplibre-gl"; +import { GeoJSONSource, LngLatBoundsLike, Map } from "maplibre-gl"; import { MapComponent, MapCursorToolTip } from "@/components/map"; import { useMapLayers } from "@/hooks/use-map-layer"; @@ -36,11 +36,13 @@ import { } from "@/utils"; import PredictedFeatureActionPopup from "@/features/start-mapping/components/popup"; import { TModelPredictionsConfig } from "@/features/start-mapping/api/get-model-predictions"; -import { startMappingPageContent, TOAST_NOTIFICATIONS } from "@/constants"; +import { TOAST_NOTIFICATIONS } from "@/constants"; import { useToolTipVisibility } from "@/hooks/use-tooltip-visibility"; import { ControlsPosition } from "@/enums"; +import useScreenSize from "@/hooks/use-screen-size"; +import { Legend } from "@/features/start-mapping/components"; -const StartMappingMapComponent = ({ +export const StartMappingMapComponent = ({ trainingDataset, modelPredictions, setModelPredictions, @@ -52,6 +54,8 @@ const StartMappingMapComponent = ({ map, mapContainerRef, currentZoom, + layers, + tmsBounds, }: { trainingDataset?: TTrainingDataset; modelPredictions: TModelPredictions; @@ -66,16 +70,17 @@ const StartMappingMapComponent = ({ map: Map | null; currentZoom: number; mapContainerRef: RefObject; + layers: { + value: string; + subLayers: string[]; + }[]; + tmsBounds: LngLatBoundsLike; }) => { const tileJSONURL = extractTileJSONURL(trainingDataset?.source_imagery ?? ""); const [showPopup, setShowPopup] = useState(false); const [selectedEvent, setSelectedEvent] = useState(null); const [selectedFeature, setSelectedFeature] = useState(null); - - const fitToTMSBounds = useCallback(() => { - if (!map || !oamTileJSON?.bounds) return; - map?.fitBounds(oamTileJSON?.bounds); - }, [map, oamTileJSON?.bounds]); + const { isSmallViewport } = useScreenSize(); const { tooltipPosition, tooltipVisible } = useToolTipVisibility(map, [ currentZoom, @@ -87,10 +92,11 @@ const StartMappingMapComponent = ({ }, [oamTileJSONIsError, oamTileJSONError]); useEffect(() => { - if (!map || !oamTileJSON?.bounds || oamTileJSONIsError) return; - fitToTMSBounds(); - }, [map, fitToTMSBounds, oamTileJSONIsError, oamTileJSON]); + if (!map || !tmsBounds || oamTileJSONIsError) return; + map.fitBounds(tmsBounds); + }, [map, tmsBounds, oamTileJSONIsError, oamTileJSON]); + // Add the map layers useMapLayers( // layers [ @@ -274,77 +280,46 @@ const StartMappingMapComponent = ({ map={map} /> ), - [selectedEvent, trainingDataset], + [ + showPopup, + selectedEvent, + selectedFeature, + setModelPredictions, + modelPredictions, + trainingDataset?.source_imagery, + trainingDataset?.id, + trainingConfig, + map, + ], ); const showTooltip = currentZoom < MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION && tooltipVisible; return ( 0 - ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .acceptedPredictions, - subLayers: [ - ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] - : []), - ...(modelPredictions.rejected.length > 0 - ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .rejectedPredictions, - subLayers: [ - REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] - : []), - ...(modelPredictions.all.length > 0 - ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .predictionResults, - subLayers: [ - ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, - ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] - : []), - ]} + showTileBoundaries + fitToBounds={!isSmallViewport} + bounds={tmsBounds} mapContainerRef={mapContainerRef} currentZoom={currentZoom} map={map} + zoomControls={!isSmallViewport} + layerControl={!isSmallViewport} + layerControlLayers={layers} + openAerialMap + basemaps + showCurrentZoom={!isSmallViewport} > {showPopup && renderPopup} {MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION} + {map && modelPredictionsExist && !isSmallViewport && } ); }; - -export default StartMappingMapComponent; diff --git a/frontend/src/features/start-mapping/components/mobile-drawer.tsx b/frontend/src/features/start-mapping/components/mobile-drawer.tsx index f24cddf9..4bc38b49 100644 --- a/frontend/src/features/start-mapping/components/mobile-drawer.tsx +++ b/frontend/src/features/start-mapping/components/mobile-drawer.tsx @@ -1,10 +1,17 @@ import { MobileDrawer } from "@/components/ui/drawer"; import { MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION } from "@/utils"; -import ModelAction from "./model-action"; -import { TModelPredictionsConfig } from "../api/get-model-predictions"; +import ModelAction from "@/features/start-mapping/components/model-action"; +import { TModelPredictionsConfig } from "@/features/start-mapping/api/get-model-predictions"; import { TModelPredictions } from "@/types"; import { Map } from "maplibre-gl"; -import { ModelDetailsButton } from "./model-details-button"; +import { ModelDetailsButton } from "@/features/start-mapping/components/model-details-button"; +import { ModelPredictionsTracker } from "@/features/start-mapping/components/model-predictions-tracker"; +import { startMappingPageContent } from "@/constants"; +import { useState } from "react"; +import { ChevronDownIcon, CloudDownloadIcon } from "@/components/ui/icons"; +import { TDownloadOptions, TQueryParams } from "@/app/routes/start-mapping"; +import { ToolTip } from "@/components/ui/tooltip"; +import { ModelSettings } from "@/features/start-mapping/components/model-settings"; export const StartMappingMobileDrawer = ({ isOpen, @@ -13,8 +20,11 @@ export const StartMappingMobileDrawer = ({ setModelPredictions, map, modelPredictions, - showModelDetails, - setShowModelDetails, + handleModelDetailsPopup, + downloadOptions, + query, + updateQuery, + modelDetailsPopupIsActive, }: { isOpen: boolean; disablePrediction: boolean; @@ -22,31 +32,89 @@ export const StartMappingMobileDrawer = ({ modelPredictions: TModelPredictions; setModelPredictions: React.Dispatch>; map: Map | null; - showModelDetails: boolean; - setShowModelDetails: (x: boolean) => void; + handleModelDetailsPopup: () => void; + modelDetailsPopupIsActive: boolean; + downloadOptions: TDownloadOptions; + query: TQueryParams; + updateQuery: (newParams: TQueryParams) => void; }) => { + const [showDownloadOptions, setShowDownloadOptions] = + useState(false); + return ( - + {disablePrediction && ( -

+

{MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION}

)} -
-
- +
+
+
+ +
+
+ +
+
+
+ {startMappingPageContent.mapData.title} -{" "} + +
+
+

Settings

+
+ +
-
- setShowModelDetails(!showModelDetails)} - showModelDetails={showModelDetails} - /> +
+ + + + {showDownloadOptions ? ( +
    + {downloadOptions + .filter((option) => option.showOnMobile) + .map((option) => ( +
  • + +
  • + ))} +
+ ) : null}
diff --git a/frontend/src/features/start-mapping/components/model-action.tsx b/frontend/src/features/start-mapping/components/model-action.tsx index 4ef180e1..ed1baa01 100644 --- a/frontend/src/features/start-mapping/components/model-action.tsx +++ b/frontend/src/features/start-mapping/components/model-action.tsx @@ -52,7 +52,7 @@ const ModelAction = ({ - - } - className="rounded-xl" - > + const modelSettings = useMemo(() => { + return (
- - ); + ); + }, [query, handleQueryUpdate, SEARCH_PARAMS]); + + if (!isMobile) { + return ( + + + + } + className="rounded-xl" + > + {modelSettings} + + ); + } + return modelSettings; }, ); diff --git a/frontend/src/features/start-mapping/components/popup.tsx b/frontend/src/features/start-mapping/components/popup.tsx index 8d9b3858..023281e5 100644 --- a/frontend/src/features/start-mapping/components/popup.tsx +++ b/frontend/src/features/start-mapping/components/popup.tsx @@ -10,11 +10,11 @@ import { useDeleteApprovedModelPrediction, useDeleteModelPredictionFeedback, } from "@/features/start-mapping/hooks/use-feedbacks"; -import { showErrorToast, showSuccessToast } from "@/utils"; +import { showErrorToast } from "@/utils"; import { geojsonToWKT } from "@terraformer/wkt"; import { useAuth } from "@/app/providers/auth-provider"; import { TModelPredictionsConfig } from "@/features/start-mapping/api/get-model-predictions"; -import { startMappingPageContent, TOAST_NOTIFICATIONS } from "@/constants"; +import { startMappingPageContent } from "@/constants"; const PredictedFeatureActionPopup = ({ event, @@ -131,9 +131,6 @@ const PredictedFeatureActionPopup = ({ accepted: updatedTarget, })); closePopup(); - showSuccessToast( - TOAST_NOTIFICATIONS.startMapping.approvedPrediction.success, - ); }, onError: (error) => { showErrorToast(error); @@ -161,7 +158,6 @@ const PredictedFeatureActionPopup = ({ rejected: updatedRejected, })); } - showSuccessToast(TOAST_NOTIFICATIONS.startMapping.resolved.success); }, onError: (error) => { showErrorToast(error); @@ -196,7 +192,6 @@ const PredictedFeatureActionPopup = ({ accepted: updatedAccepted, })); } - showSuccessToast(TOAST_NOTIFICATIONS.startMapping.resolved.success); }, onError: (error) => { showErrorToast(error); @@ -253,7 +248,6 @@ const PredictedFeatureActionPopup = ({ })); } closePopup(); - showSuccessToast(TOAST_NOTIFICATIONS.startMapping.feedback.success); }, onError: (error) => { showErrorToast(error); @@ -347,7 +341,7 @@ const PredictedFeatureActionPopup = ({ return (
diff --git a/frontend/src/hooks/use-map-instance.ts b/frontend/src/hooks/use-map-instance.ts index 38703232..56507e03 100644 --- a/frontend/src/hooks/use-map-instance.ts +++ b/frontend/src/hooks/use-map-instance.ts @@ -20,11 +20,7 @@ export const useMapInstance = (pmtiles: boolean = false) => { ); useEffect(() => { - const map = setupMaplibreMap( - mapContainerRef, - MAP_STYLES[BASEMAPS.OSM], - pmtiles, - ); + const map = setupMaplibreMap(mapContainerRef, pmtiles); map.on("load", () => { setMap(map); diff --git a/frontend/src/hooks/use-map-layer.ts b/frontend/src/hooks/use-map-layer.ts index f492f00f..62f01217 100644 --- a/frontend/src/hooks/use-map-layer.ts +++ b/frontend/src/hooks/use-map-layer.ts @@ -14,7 +14,7 @@ export const useMapLayers = ( }, [map, sourcesSpec, layersSpec]); useEffect(() => { - if (!map) return; + if (!map || !map.isStyleLoaded()) return; if (!map.isStyleLoaded()) { map.once("styledata", addSourcesAndLayers); } else { diff --git a/frontend/src/hooks/use-screen-size.ts b/frontend/src/hooks/use-screen-size.ts index c5bdbef0..51c3a12f 100644 --- a/frontend/src/hooks/use-screen-size.ts +++ b/frontend/src/hooks/use-screen-size.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; /** * Custom hook to detect whether the current device is mobile, tablet based on the window width. @@ -37,7 +37,12 @@ const useScreenSize = () => { }; }, []); - return screenSize; + const isSmallViewport = useMemo( + () => screenSize.isMobile || screenSize.isTablet, + [screenSize], + ); + + return { ...screenSize, isSmallViewport }; }; export default useScreenSize; diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index 1243d2b5..19d7e065 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -58,6 +58,8 @@ body { /* 80px */ --hot-fair-spacing-large: 2rem; /* 32px */ + --hot-fair-spacing-small: 1rem; + /* 16px */ /* Scroll bar styles */ --scrollbar-width: 6px; @@ -68,11 +70,22 @@ body { /* Toast stack placement style begins */ +/* On Mobile show at the top. */ .sl-toast-stack { left: 0; right: auto; - top: auto; - bottom: 0; + top: 0; + bottom: auto; +} + +/* On Web, show at the bottom. */ +@media (min-width: 768px) { + .sl-toast-stack { + left: 0; + right: auto; + top: auto; + bottom: 0; + } } /* Toast stack placement style ends */ @@ -156,10 +169,10 @@ sl-alert.success::part(base) { @apply inline-block h-4 w-4 p-2; } .fullscreen { - @apply -mx-large 2xl:-mx-extra-large; + @apply -mx-small md:-mx-large 2xl:-mx-extra-large; } .app-padding { - @apply px-large 2xl:px-extra-large; + @apply px-small md:px-large 2xl:px-extra-large; } .no-fullscreen { @apply -mx-0 lg:-mx-0; @@ -169,7 +182,13 @@ sl-alert.success::part(base) { @apply flex flex-col gap-y-20 md:gap-y-40 mb-48; } .icon-interaction { - @apply bg-light-gray rounded-lg; + @apply bg-off-white rounded-lg; + } + .map-elements-z-index { + @apply z-[1]; + } + .map-elements-padding { + @apply px-3; } } diff --git a/frontend/src/types/ui-contents.ts b/frontend/src/types/ui-contents.ts index 776d522d..3d84569d 100644 --- a/frontend/src/types/ui-contents.ts +++ b/frontend/src/types/ui-contents.ts @@ -30,6 +30,7 @@ export type TStartMappingPageContent = { }; legendControl: { title: string; + tooltip: string; acceptedPredictions: string; rejectedPredictions: string; predictionResults: string; @@ -94,7 +95,7 @@ export type TStartMappingPageContent = { rejected: string; }; actions: { - disabledModeTooltip: string; + disabledModeTooltip: (p: string) => string; }; modelDetails: { error: string; diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index e5dad2db..6a10bd32 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -155,7 +155,7 @@ export const MIN_ZOOM_LEVEL_FOR_TRAINING_AREA_LABELS = export const TILE_BOUNDARY_LAYER_ID = `${MAP_STYLES_PREFIX}-tile-boundary-layer`; export const TILE_BOUNDARY_SOURCE_ID = `${MAP_STYLES_PREFIX}-tile-boundaries`; -export const TMS_LAYER_ID = `${MAP_STYLES_PREFIX}-training-dataset-tms-layer`; +export const TMS_LAYER_ID = `${MAP_STYLES_PREFIX}-oam-tms-layer`; export const TMS_SOURCE_ID = `${MAP_STYLES_PREFIX}-oam-training-dataset`; export const OSM_BASEMAP_LAYER_ID = `${MAP_STYLES_PREFIX}-osm-layer`; export const GOOGLE_SATELLITE_BASEMAP_LAYER_ID = `${MAP_STYLES_PREFIX}-google-statellite-layer`; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 8bc7e94d..115c0f7e 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -43,6 +43,7 @@ export default { spacing: { "extra-large": "var(--hot-fair-spacing-extra-large)", large: "var(--hot-fair-spacing-large)", + small: "var(--hot-fair-spacing-small)", }, }, }, From 2de8d2942227060b755577c451c81f5bd469cae6 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Wed, 25 Dec 2024 12:24:04 +0100 Subject: [PATCH 09/33] chore: fixed bugs --- frontend/src/app/routes/start-mapping.tsx | 62 +++++++++---------- .../components/filters/category-filter.tsx | 2 +- .../components/filters/status-filter.tsx | 1 - .../start-mapping/components/header.tsx | 6 +- frontend/src/hooks/use-map-instance.ts | 3 +- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index ba138485..2d878acf 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -36,7 +36,7 @@ import { UserProfile } from "@/components/layout"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { FitToBounds, LayerControl, ZoomLevel } from "@/components/map"; import { LngLatBoundsLike } from "maplibre-gl"; -import { MobileDrawer } from "@/components/ui/drawer"; + export type TDownloadOptions = { name: string; @@ -170,42 +170,42 @@ export const StartMappingPage = () => { () => [ ...(modelPredictions.accepted.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .acceptedPredictions, - subLayers: [ - ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .acceptedPredictions, + subLayers: [ + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ...(modelPredictions.rejected.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .rejectedPredictions, - subLayers: [ - REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .rejectedPredictions, + subLayers: [ + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ...(modelPredictions.all.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .predictionResults, - subLayers: [ - ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, - ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .predictionResults, + subLayers: [ + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ], [modelPredictions, startMappingPageContent], diff --git a/frontend/src/features/models/components/filters/category-filter.tsx b/frontend/src/features/models/components/filters/category-filter.tsx index 4383bbfd..56a9f49d 100644 --- a/frontend/src/features/models/components/filters/category-filter.tsx +++ b/frontend/src/features/models/components/filters/category-filter.tsx @@ -49,7 +49,7 @@ const CategoryFilter: React.FC = ({ disabled={disabled} withCheckbox defaultSelectedItem={categories[0].value} - menuItemTextSize="small" + triggerComponent={

{categories[0].value} diff --git a/frontend/src/features/models/components/filters/status-filter.tsx b/frontend/src/features/models/components/filters/status-filter.tsx index adda2ff4..3f7d0054 100644 --- a/frontend/src/features/models/components/filters/status-filter.tsx +++ b/frontend/src/features/models/components/filters/status-filter.tsx @@ -80,7 +80,6 @@ const StatusFilter: React.FC = ({ disabled={disabled} defaultSelectedItem={categoryLabel[0]?.value} withCheckbox - menuItemTextSize="small" triggerComponent={

{categoryLabel[0]?.value} diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index 462ab2e4..aa1b082f 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -17,7 +17,7 @@ import { ToolTip } from "@/components/ui/tooltip"; import { ModelDetailsButton } from "@/features/start-mapping/components/model-details-button"; import { BrandLogoWithDropDown } from "./logo-with-dropdown"; import { ModelPredictionsTracker } from "@/features/start-mapping/components/model-predictions-tracker"; -import { MobileDrawer } from "@/components/ui/drawer"; + const StartMappingHeader = ({ data, @@ -100,8 +100,8 @@ const StartMappingHeader = ({ content={ !modelPredictionsExist ? startMappingPageContent.actions.disabledModeTooltip( - "see actions", - ) + "see actions", + ) : null } > diff --git a/frontend/src/hooks/use-map-instance.ts b/frontend/src/hooks/use-map-instance.ts index 56507e03..c5ab0e1d 100644 --- a/frontend/src/hooks/use-map-instance.ts +++ b/frontend/src/hooks/use-map-instance.ts @@ -1,7 +1,6 @@ import { setupMaplibreMap } from "@/components/map/setups/setup-maplibre"; import { setupTerraDraw } from "@/components/map/setups/setup-terra-draw"; -import { BASEMAPS, DrawingModes } from "@/enums"; -import { MAP_STYLES } from "@/utils"; +import { DrawingModes } from "@/enums"; import { Map } from "maplibre-gl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; From ca9953e799a433da431ec97a96af6a29af1f2a7b Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Thu, 26 Dec 2024 11:38:37 +0100 Subject: [PATCH 10/33] wip: mobile fixes --- frontend/src/app/routes/start-mapping.tsx | 23 +++- .../map/controls/current-zoom-control.tsx | 2 +- .../map/controls/fit-to-bounds-control.tsx | 2 +- .../components/map/controls/layer-control.tsx | 14 +-- .../components/map/layers/tile-boundaries.tsx | 2 +- .../components/ui/drawer/mobile-drawer.tsx | 18 +-- .../ui-contents/start-mapping-content.ts | 1 - .../models/components/model-details-popup.tsx | 9 +- .../start-mapping/components/header.tsx | 7 +- .../components/map/legend-control.tsx | 103 ++++++++---------- .../components/mobile-drawer.tsx | 12 +- .../start-mapping/components/model-action.tsx | 2 +- .../components/model-details-button.tsx | 4 +- .../components/model-predictions-tracker.tsx | 22 +++- frontend/src/styles/index.css | 4 +- frontend/src/types/ui-contents.ts | 1 - 16 files changed, 124 insertions(+), 102 deletions(-) diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index 2d878acf..49b74d52 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -303,6 +303,14 @@ export const StartMappingPage = () => { setShowModelDetailsPopup((prev) => !prev); }, [setShowModelDetailsPopup]); + const clearPredictions = useCallback(() => { + setModelPredictions(({ + accepted: [], + rejected: [], + all: [] + })) + }, [setModelPredictions]); + return ( <> @@ -320,6 +328,7 @@ export const StartMappingPage = () => { query={query} updateQuery={updateQuery} modelDetailsPopupIsActive={showModelDetailsPopup} + clearPredictions={clearPredictions} />

{/* Model Details Popup */} @@ -352,10 +361,11 @@ export const StartMappingPage = () => { modelDetailsPopupIsActive={showModelDetailsPopup} handleModelDetailsPopup={handleModelDetailsPopup} downloadOptions={downloadOptions} + clearPredictions={clearPredictions} />
- {/* Mobile Header and Controls */} + {/* Mobile Header and Map Controls */}
@@ -367,19 +377,22 @@ export const StartMappingPage = () => { isOpened={dropdownIsOpened} />
-
+
-
+
+
+ {map && modelPredictionsExist && } +
- {map && modelPredictionsExist && } +
{/* Map Component */} { return ( -
+

Zoom level: {currentZoom}

); diff --git a/frontend/src/components/map/controls/fit-to-bounds-control.tsx b/frontend/src/components/map/controls/fit-to-bounds-control.tsx index 4b0300af..70ef7253 100644 --- a/frontend/src/components/map/controls/fit-to-bounds-control.tsx +++ b/frontend/src/components/map/controls/fit-to-bounds-control.tsx @@ -22,7 +22,7 @@ export const FitToBounds = ({ return ( - {!isSmallViewport && ( -

- {startMappingPageContent.map.controls.legendControl.title} - -

- )} - {expandLegend ? ( -
- {activeFillLayers?.map((layer, id) => ( -

- {layer.type === "fill" ? ( - - ) : null} - {LEGEND_NAME_MAPPING[layer.id]} -

- ))} -
- ) : null} - {expandLegend && isSmallViewport && ( - - )} - -
); }; diff --git a/frontend/src/features/start-mapping/components/mobile-drawer.tsx b/frontend/src/features/start-mapping/components/mobile-drawer.tsx index 4bc38b49..8aaa60d9 100644 --- a/frontend/src/features/start-mapping/components/mobile-drawer.tsx +++ b/frontend/src/features/start-mapping/components/mobile-drawer.tsx @@ -25,6 +25,7 @@ export const StartMappingMobileDrawer = ({ query, updateQuery, modelDetailsPopupIsActive, + clearPredictions }: { isOpen: boolean; disablePrediction: boolean; @@ -37,6 +38,7 @@ export const StartMappingMobileDrawer = ({ downloadOptions: TDownloadOptions; query: TQueryParams; updateQuery: (newParams: TQueryParams) => void; + clearPredictions: () => void }) => { const [showDownloadOptions, setShowDownloadOptions] = useState(false); @@ -48,7 +50,7 @@ export const StartMappingMobileDrawer = ({ {MINIMUM_ZOOM_LEVEL_INSTRUCTION_FOR_PREDICTION}

)} -
+
{startMappingPageContent.mapData.title} -{" "} - +

Settings

@@ -81,14 +83,14 @@ export const StartMappingMobileDrawer = ({ content={ disablePrediction ? startMappingPageContent.actions.disabledModeTooltip( - "see download options", - ) + "see download options", + ) : null } > + : null + } +
); }; diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index 19d7e065..9c21c422 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -95,7 +95,7 @@ body { sl-alert::part(close-button__base), sl-alert::part(close-button__base):hover { color: var(--hot-fair-color-dark); - font-size: var(--hot-fair-font-size-title-3); + font-size: var(--hot-fair-font-size-title-4); } sl-alert::part(base) { @@ -140,7 +140,7 @@ sl-alert.success::part(base) { .hot-matomo { position: fixed; - z-index: 10000; + z-index: 1000000000000; bottom: 0; width: 100vw; } diff --git a/frontend/src/types/ui-contents.ts b/frontend/src/types/ui-contents.ts index 3d84569d..6ab01580 100644 --- a/frontend/src/types/ui-contents.ts +++ b/frontend/src/types/ui-contents.ts @@ -30,7 +30,6 @@ export type TStartMappingPageContent = { }; legendControl: { title: string; - tooltip: string; acceptedPredictions: string; rejectedPredictions: string; predictionResults: string; From 9f96209a564d543d987a3d81afc9af6a1d566c1f Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Thu, 26 Dec 2024 11:41:17 +0100 Subject: [PATCH 11/33] chore: fixed model details popup --- frontend/src/features/models/components/model-details-popup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index 0de0887f..d8d021a4 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -49,7 +49,6 @@ const ModelDetailsPopUp = ({

- {" "} {startMappingPageContent.modelDetails.popover.modelId}:{" "} {model?.id ?? data?.id}

From 03d3429a5da154bb4ec92d38c48fa5f9192f7c8b Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Thu, 26 Dec 2024 13:21:09 +0100 Subject: [PATCH 12/33] chore: made my models authenticated --- frontend/src/app/router.tsx | 2 +- .../training-area/open-area-map.tsx | 2 +- .../training-area/training-area-map.tsx | 44 +++++++++----- .../models/components/model-details-popup.tsx | 2 +- frontend/src/hooks/use-layer-order.ts | 58 +++++++++++++++++++ 5 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 frontend/src/hooks/use-layer-order.ts diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 24f76504..3dea207c 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -309,7 +309,7 @@ const router = createBrowserRouter([ "@/app/routes/account/models" ); return { - Component: () => , + Component: () => }; }, }, diff --git a/frontend/src/features/model-creation/components/training-area/open-area-map.tsx b/frontend/src/features/model-creation/components/training-area/open-area-map.tsx index 9de4fb48..157f0378 100644 --- a/frontend/src/features/model-creation/components/training-area/open-area-map.tsx +++ b/frontend/src/features/model-creation/components/training-area/open-area-map.tsx @@ -53,7 +53,7 @@ const OpenAerialMap = ({ className="basis-4/5 text-start text-body-3 text-wrap w-full" title={data?.name} > - {truncateString(data?.name, 30)} + {truncateString(data?.name, 40)}

{ if (map) { if (map.getSource(trainingDatasetLabelsSourceId) && labels) { @@ -203,7 +213,9 @@ const TrainingAreaMap = ({ updateTrainingLabels(); }, [labels]); - // drawing events and tooltip + /** + * Drawing events and tooltip + */ useEffect(() => { if (!terraDraw || !map) return; @@ -300,22 +312,22 @@ const TrainingAreaMap = ({ layerControlLayers={[ ...(data?.results?.features?.length ? [ - { - value: "Training Areas", - subLayers: [trainingAreasLayerId, trainingAreasFillLayerId], - }, - ] + { + value: "Training Areas", + subLayers: [trainingAreasLayerId, trainingAreasFillLayerId], + }, + ] : []), ...(labels && labels?.features.length > 0 ? [ - { - value: "Training Labels", - subLayers: [ - trainingDatasetLabelsLayerId, - trainingDatasetLabelsOutlineLayerId, - ], - }, - ] + { + value: "Training Labels", + subLayers: [ + trainingDatasetLabelsLayerId, + trainingDatasetLabelsOutlineLayerId, + ], + }, + ] : []), ]} > diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index d8d021a4..baca322d 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -101,7 +101,7 @@ const ModelDetailsPopUp = ({ showSkeleton={trainingDetailsIsPending} skeletonClassName="w-20 h-4" > - + {trainingDetailsError ? "N/A" : trainingDetails?.zoom_level?.join(", ")}{" "} diff --git a/frontend/src/hooks/use-layer-order.ts b/frontend/src/hooks/use-layer-order.ts new file mode 100644 index 00000000..411c9f2c --- /dev/null +++ b/frontend/src/hooks/use-layer-order.ts @@ -0,0 +1,58 @@ +import { useEffect, useRef } from "react"; +import { Map } from "maplibre-gl"; +import { TILE_BOUNDARY_LAYER_ID, TMS_LAYER_ID } from "@/utils"; + +type UseLayerReorderProps = { + /** IDs of all feature layers. (We want them above TMS.) */ + featureLayerIds: string[]; +} + +/** + * Reorders layers in MapLibre so that: + * - The TMS layer is below all feature layers. + * - The boundary line layer is on top of everything. + */ +export const useLayerReorder = ( + map: Map | null, + { + featureLayerIds, + }: UseLayerReorderProps +) => { + const didReorder = useRef(false); + + useEffect(() => { + if (!map) return; + + const reorderLayers = () => { + if (!map.isStyleLoaded()) return; + // Move each feature layer on top of the TMS + featureLayerIds.forEach((featureId) => { + if (map.getLayer(featureId)) { + try { + map.moveLayer(featureId); + } catch (err) { + console.warn(`Error moving feature ${featureId} above TMS:`, err); + } + } + }); + try { + map.moveLayer(TILE_BOUNDARY_LAYER_ID); + } catch (err) { + console.warn("Error moving boundary line to top:", err); + } + didReorder.current = true; + }; + // Re-run ordering whenever style data changes (and if not done already). + const handleStyleData = () => { + if (!didReorder.current) { + reorderLayers(); + } + }; + + map.on("styledata", handleStyleData); + + return () => { + map.off("styledata", handleStyleData); + }; + }, [map, featureLayerIds, TMS_LAYER_ID, TILE_BOUNDARY_LAYER_ID]); +} From 38d01f349d13a3303928021fab1a257b2fb2d14a Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Thu, 26 Dec 2024 13:58:46 +0100 Subject: [PATCH 13/33] chore: added dropdown options to logo menu --- frontend/src/app/index.tsx | 21 +---- frontend/src/app/router.tsx | 6 +- frontend/src/app/routes/start-mapping.tsx | 72 ++++++++--------- .../src/components/layout/navbar/navbar.tsx | 27 +------ .../components/layout/navbar/user-profile.tsx | 3 +- .../components/map/controls/layer-control.tsx | 12 +-- .../src/components/shared/hot-tracking.tsx | 27 +++++++ frontend/src/components/shared/index.ts | 1 + .../components/ui/drawer/mobile-drawer.tsx | 17 ++-- .../src/components/ui/dropdown/dropdown.tsx | 5 +- frontend/src/constants/common.ts | 21 +++++ frontend/src/enums/common.ts | 6 ++ .../training-area/training-area-map.tsx | 5 +- .../components/filters/category-filter.tsx | 1 - .../models/components/layout-toggle.tsx | 9 ++- .../start-mapping/components/header.tsx | 14 ++-- .../components/logo-with-dropdown.tsx | 70 ++++++++++------ .../components/map/legend-control.tsx | 19 ++--- .../components/mobile-drawer.tsx | 13 +-- .../components/model-details-button.tsx | 8 +- .../components/model-predictions-tracker.tsx | 16 ++-- .../components/model-settings.tsx | 6 +- frontend/src/hooks/use-layer-order.ts | 80 +++++++++---------- frontend/src/layouts/root-layout.tsx | 30 ++++--- frontend/src/types/common.ts | 5 ++ 25 files changed, 278 insertions(+), 216 deletions(-) create mode 100644 frontend/src/components/shared/hot-tracking.tsx create mode 100644 frontend/src/constants/common.ts diff --git a/frontend/src/app/index.tsx b/frontend/src/app/index.tsx index 5a1245e2..da83bedb 100644 --- a/frontend/src/app/index.tsx +++ b/frontend/src/app/index.tsx @@ -1,32 +1,13 @@ import { AppRouter } from "@/app/router"; -import { useEffect } from "react"; -import { ENVS } from "@/config/env"; import { QueryCache, QueryClient, QueryClientProvider, } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { HOT_TRACKING_HTML_TAG_NAME, showErrorToast } from "@/utils"; +import { showErrorToast } from "@/utils"; export const App = () => { - const setupHotTracking = () => { - const hotTracking = document.createElement(HOT_TRACKING_HTML_TAG_NAME); - // adding a css class to style the component in the `styles/index.css` file. - hotTracking.classList.add("hot-matomo"); - // setting the other attributes. - hotTracking.setAttribute("site-id", ENVS.MATOMO_ID); - hotTracking.setAttribute("domain", ENVS.MATOMO_APP_DOMAIN); - hotTracking.setAttribute("force", "true"); - document.body.appendChild(hotTracking); - }; - useEffect(() => { - if (document.getElementsByTagName(HOT_TRACKING_HTML_TAG_NAME).length > 0) - return; - setupHotTracking(); - return; - }, []); - const queryClient = new QueryClient({ queryCache: new QueryCache({ onError: (error, query) => { diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 3dea207c..256a85a2 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -309,7 +309,11 @@ const router = createBrowserRouter([ "@/app/routes/account/models" ); return { - Component: () => + Component: () => ( + + + + ), }; }, }, diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index 49b74d52..1d194ac5 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -37,7 +37,6 @@ import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { FitToBounds, LayerControl, ZoomLevel } from "@/components/map"; import { LngLatBoundsLike } from "maplibre-gl"; - export type TDownloadOptions = { name: string; value: string; @@ -170,42 +169,42 @@ export const StartMappingPage = () => { () => [ ...(modelPredictions.accepted.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .acceptedPredictions, - subLayers: [ - ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .acceptedPredictions, + subLayers: [ + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ...(modelPredictions.rejected.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .rejectedPredictions, - subLayers: [ - REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .rejectedPredictions, + subLayers: [ + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ...(modelPredictions.all.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .predictionResults, - subLayers: [ - ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, - ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .predictionResults, + subLayers: [ + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ], [modelPredictions, startMappingPageContent], @@ -304,11 +303,11 @@ export const StartMappingPage = () => { }, [setShowModelDetailsPopup]); const clearPredictions = useCallback(() => { - setModelPredictions(({ + setModelPredictions({ accepted: [], rejected: [], - all: [] - })) + all: [], + }); }, [setModelPredictions]); return ( @@ -388,11 +387,8 @@ export const StartMappingPage = () => {
-
- {map && modelPredictionsExist && } -
+
{map && modelPredictionsExist && }
-
{/* Map Component */} { const [open, setOpen] = useState(false); @@ -84,30 +85,6 @@ export const NavBar = () => { ); }; -type TNavBarLinks = { - title: string; - href: string; -}[]; - -const navLinks: TNavBarLinks = [ - { - title: APP_CONTENT.navbar.routes.exploreModels, - href: APPLICATION_ROUTES.MODELS, - }, - { - title: APP_CONTENT.navbar.routes.learn, - href: APPLICATION_ROUTES.LEARN, - }, - { - title: APP_CONTENT.navbar.routes.about, - href: APPLICATION_ROUTES.ABOUT, - }, - { - title: APP_CONTENT.navbar.routes.resources, - href: APPLICATION_ROUTES.RESOURCES, - }, -]; - type NavBarLinksProps = { className: string; setOpen?: (arg: boolean) => void; diff --git a/frontend/src/components/layout/navbar/user-profile.tsx b/frontend/src/components/layout/navbar/user-profile.tsx index 15910d48..927faf9e 100644 --- a/frontend/src/components/layout/navbar/user-profile.tsx +++ b/frontend/src/components/layout/navbar/user-profile.tsx @@ -6,6 +6,7 @@ import { APP_CONTENT, APPLICATION_ROUTES, truncateString } from "@/utils"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { useAuth } from "@/app/providers/auth-provider"; import useScreenSize from "@/hooks/use-screen-size"; +import { DropdownPlacement } from "@/enums"; export const UserProfile = ({ hideFullName, @@ -51,7 +52,7 @@ export const UserProfile = ({ }, ]} distance={10} - placement="bottom-end" + placement={DropdownPlacement.BOTTOM_END} triggerComponent={
{ + const { pathname } = useLocation(); + + useEffect(() => { + if (document.getElementsByTagName(HOT_TRACKING_HTML_TAG_NAME).length > 0) + return; + + if (pathname === homepagePath) { + const hotTracking = document.createElement(HOT_TRACKING_HTML_TAG_NAME); + // CSS classname to customize it + hotTracking.classList.add("hot-matomo"); + hotTracking.setAttribute("site-id", ENVS.MATOMO_ID); + hotTracking.setAttribute("domain", ENVS.MATOMO_APP_DOMAIN); + hotTracking.setAttribute("force", "true"); + + // Append element to body + document.body.appendChild(hotTracking); + } + }, [pathname, homepagePath]); + + return null; +}; diff --git a/frontend/src/components/shared/index.ts b/frontend/src/components/shared/index.ts index 745734d5..e4a50853 100644 --- a/frontend/src/components/shared/index.ts +++ b/frontend/src/components/shared/index.ts @@ -3,3 +3,4 @@ export { FAQs } from "./faqs/faqs"; export { SectionHeader } from "./section-header"; export * from "./pagination"; export { TheFAIRProcess } from "./fair-process/fair-process"; +export { HotTracking } from "./hot-tracking"; diff --git a/frontend/src/components/ui/drawer/mobile-drawer.tsx b/frontend/src/components/ui/drawer/mobile-drawer.tsx index 7b7f8001..6ac6da54 100644 --- a/frontend/src/components/ui/drawer/mobile-drawer.tsx +++ b/frontend/src/components/ui/drawer/mobile-drawer.tsx @@ -8,7 +8,7 @@ export const MobileDrawer = ({ dialogTitle, canClose = false, closeDrawer, - snapPoints = [0.2, 0.4, 0.8] + snapPoints = [0.2, 0.4, 0.8], }: { open: boolean; children: React.ReactNode; @@ -16,13 +16,11 @@ export const MobileDrawer = ({ closeDrawer?: () => void; canClose?: boolean; startingSnapPoint?: number | string; - snapPoints?: number[] + snapPoints?: number[]; }) => { - const [snap, setSnap] = useState(snapPoints[0]); const lastSnapPoint = snapPoints[snapPoints.length - 1]; - return ( - + + {" "} + ✕ + ) : null} - +
) => void; onDropdownHide?: (event: React.MouseEvent) => void; @@ -39,7 +40,7 @@ type DropDownProps = { const DropDown: React.FC = ({ children, menuItems, - placement = "bottom-start", + placement = DropdownPlacement.BOTTOM_START, onDropdownHide, onDropdownShow, dropdownIsOpened, diff --git a/frontend/src/constants/common.ts b/frontend/src/constants/common.ts new file mode 100644 index 00000000..51ae9556 --- /dev/null +++ b/frontend/src/constants/common.ts @@ -0,0 +1,21 @@ +import { TNavBarLinks } from "@/types"; +import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; + +export const navLinks: TNavBarLinks = [ + { + title: APP_CONTENT.navbar.routes.exploreModels, + href: APPLICATION_ROUTES.MODELS, + }, + { + title: APP_CONTENT.navbar.routes.learn, + href: APPLICATION_ROUTES.LEARN, + }, + { + title: APP_CONTENT.navbar.routes.about, + href: APPLICATION_ROUTES.ABOUT, + }, + { + title: APP_CONTENT.navbar.routes.resources, + href: APPLICATION_ROUTES.RESOURCES, + }, +]; diff --git a/frontend/src/enums/common.ts b/frontend/src/enums/common.ts index c3605201..84ae6008 100644 --- a/frontend/src/enums/common.ts +++ b/frontend/src/enums/common.ts @@ -53,3 +53,9 @@ export enum DrawerPlacements { TOP = "top", END = "end", } + +export enum DropdownPlacement { + BOTTOM_START = "bottom-start", + BOTTOM_END = 'bottom-end', + TOP_END = 'top-end' +} \ No newline at end of file diff --git a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx index dfeb651c..0b252136 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx @@ -31,7 +31,6 @@ import { ControlsPosition, DrawingModes } from "@/enums"; import { useToolTipVisibility } from "@/hooks/use-tooltip-visibility"; import { useMapLayers } from "@/hooks/use-map-layer"; import { TerraDraw } from "terra-draw"; -import { useLayerReorder } from "@/hooks/use-layer-order"; // Debounce delay in milliseconds. @@ -214,8 +213,8 @@ const TrainingAreaMap = ({ }, [labels]); /** - * Drawing events and tooltip - */ + * Drawing events and tooltip + */ useEffect(() => { if (!terraDraw || !map) return; diff --git a/frontend/src/features/models/components/filters/category-filter.tsx b/frontend/src/features/models/components/filters/category-filter.tsx index 56a9f49d..57128167 100644 --- a/frontend/src/features/models/components/filters/category-filter.tsx +++ b/frontend/src/features/models/components/filters/category-filter.tsx @@ -49,7 +49,6 @@ const CategoryFilter: React.FC = ({ disabled={disabled} withCheckbox defaultSelectedItem={categories[0].value} - triggerComponent={

{categories[0].value} diff --git a/frontend/src/features/models/components/layout-toggle.tsx b/frontend/src/features/models/components/layout-toggle.tsx index 32beb59f..d6a27b0b 100644 --- a/frontend/src/features/models/components/layout-toggle.tsx +++ b/frontend/src/features/models/components/layout-toggle.tsx @@ -1,6 +1,7 @@ import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { CategoryIcon, ListIcon } from "@/components/ui/icons"; import { LayoutView } from "@/enums"; +import { useScrollToTop } from "@/hooks/use-scroll-to-element"; import { TQueryParams } from "@/types"; const LayoutToggle = ({ @@ -15,18 +16,20 @@ const LayoutToggle = ({ disabled?: boolean; }) => { const activeLayout = query[SEARCH_PARAMS.layout]; + const { scrollToTop } = useScrollToTop(); return ( -

); -}; +}); diff --git a/frontend/src/features/start-mapping/components/map/legend-control.tsx b/frontend/src/features/start-mapping/components/map/legend-control.tsx index aa7f5587..88b9f9b0 100644 --- a/frontend/src/features/start-mapping/components/map/legend-control.tsx +++ b/frontend/src/features/start-mapping/components/map/legend-control.tsx @@ -27,14 +27,16 @@ const FillLegendStyle = ({ export const Legend = ({ map }: { map: Map | null }) => { const [expandLegend, setExpandLegend] = useState(true); - const activeFillLayers = map?.getStyle() - .layers?.filter( - (layer) => - layer.id.includes(MAP_STYLES_PREFIX) && - layer.layout?.visibility === "visible" && - layer.type === "fill", - ) - .reverse() || [] + const activeFillLayers = + map + ?.getStyle() + .layers?.filter( + (layer) => + layer.id.includes(MAP_STYLES_PREFIX) && + layer.layout?.visibility === "visible" && + layer.type === "fill", + ) + .reverse() || []; const handleToggleExpand = useCallback(() => { setExpandLegend((prev) => !prev); @@ -81,6 +83,5 @@ export const Legend = ({ map }: { map: Map | null }) => { ) : null} - ); }; diff --git a/frontend/src/features/start-mapping/components/mobile-drawer.tsx b/frontend/src/features/start-mapping/components/mobile-drawer.tsx index 8aaa60d9..87c0f752 100644 --- a/frontend/src/features/start-mapping/components/mobile-drawer.tsx +++ b/frontend/src/features/start-mapping/components/mobile-drawer.tsx @@ -25,7 +25,7 @@ export const StartMappingMobileDrawer = ({ query, updateQuery, modelDetailsPopupIsActive, - clearPredictions + clearPredictions, }: { isOpen: boolean; disablePrediction: boolean; @@ -38,7 +38,7 @@ export const StartMappingMobileDrawer = ({ downloadOptions: TDownloadOptions; query: TQueryParams; updateQuery: (newParams: TQueryParams) => void; - clearPredictions: () => void + clearPredictions: () => void; }) => { const [showDownloadOptions, setShowDownloadOptions] = useState(false); @@ -70,7 +70,10 @@ export const StartMappingMobileDrawer = ({
{startMappingPageContent.mapData.title} -{" "} - +

Settings

@@ -83,8 +86,8 @@ export const StartMappingMobileDrawer = ({ content={ disablePrediction ? startMappingPageContent.actions.disabledModeTooltip( - "see download options", - ) + "see download options", + ) : null } > diff --git a/frontend/src/features/start-mapping/components/model-details-button.tsx b/frontend/src/features/start-mapping/components/model-details-button.tsx index c619bbdf..c7144f0c 100644 --- a/frontend/src/features/start-mapping/components/model-details-button.tsx +++ b/frontend/src/features/start-mapping/components/model-details-button.tsx @@ -12,9 +12,13 @@ export const ModelDetailsButton = ({ onClick: () => void; modelDetailsPopupIsActive?: boolean; }) => { - const { isSmallViewport } = useScreenSize() + const { isSmallViewport } = useScreenSize(); return ( - + - : null - } + ) : null}
); }; diff --git a/frontend/src/features/start-mapping/components/model-settings.tsx b/frontend/src/features/start-mapping/components/model-settings.tsx index dcda4900..641fc26f 100644 --- a/frontend/src/features/start-mapping/components/model-settings.tsx +++ b/frontend/src/features/start-mapping/components/model-settings.tsx @@ -4,7 +4,7 @@ import { FormLabel, Input, Select, Switch } from "@/components/ui/form"; import { SettingsIcon } from "@/components/ui/icons"; import { ToolTip } from "@/components/ui/tooltip"; import { startMappingPageContent } from "@/constants"; -import { INPUT_TYPES, SHOELACE_SIZES } from "@/enums"; +import { DropdownPlacement, INPUT_TYPES, SHOELACE_SIZES } from "@/enums"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { memo, useMemo } from "react"; @@ -147,7 +147,7 @@ export const ModelSettings = memo( if (!isMobile) { return ( - + } diff --git a/frontend/src/hooks/use-layer-order.ts b/frontend/src/hooks/use-layer-order.ts index 411c9f2c..5d317202 100644 --- a/frontend/src/hooks/use-layer-order.ts +++ b/frontend/src/hooks/use-layer-order.ts @@ -3,9 +3,9 @@ import { Map } from "maplibre-gl"; import { TILE_BOUNDARY_LAYER_ID, TMS_LAYER_ID } from "@/utils"; type UseLayerReorderProps = { - /** IDs of all feature layers. (We want them above TMS.) */ - featureLayerIds: string[]; -} + /** IDs of all feature layers. (We want them above TMS.) */ + featureLayerIds: string[]; +}; /** * Reorders layers in MapLibre so that: @@ -13,46 +13,44 @@ type UseLayerReorderProps = { * - The boundary line layer is on top of everything. */ export const useLayerReorder = ( - map: Map | null, - { - featureLayerIds, - }: UseLayerReorderProps + map: Map | null, + { featureLayerIds }: UseLayerReorderProps, ) => { - const didReorder = useRef(false); + const didReorder = useRef(false); - useEffect(() => { - if (!map) return; + useEffect(() => { + if (!map) return; - const reorderLayers = () => { - if (!map.isStyleLoaded()) return; - // Move each feature layer on top of the TMS - featureLayerIds.forEach((featureId) => { - if (map.getLayer(featureId)) { - try { - map.moveLayer(featureId); - } catch (err) { - console.warn(`Error moving feature ${featureId} above TMS:`, err); - } - } - }); - try { - map.moveLayer(TILE_BOUNDARY_LAYER_ID); - } catch (err) { - console.warn("Error moving boundary line to top:", err); - } - didReorder.current = true; - }; - // Re-run ordering whenever style data changes (and if not done already). - const handleStyleData = () => { - if (!didReorder.current) { - reorderLayers(); - } - }; + const reorderLayers = () => { + if (!map.isStyleLoaded()) return; + // Move each feature layer on top of the TMS + featureLayerIds.forEach((featureId) => { + if (map.getLayer(featureId)) { + try { + map.moveLayer(featureId); + } catch (err) { + console.warn(`Error moving feature ${featureId} above TMS:`, err); + } + } + }); + try { + map.moveLayer(TILE_BOUNDARY_LAYER_ID); + } catch (err) { + console.warn("Error moving boundary line to top:", err); + } + didReorder.current = true; + }; + // Re-run ordering whenever style data changes (and if not done already). + const handleStyleData = () => { + if (!didReorder.current) { + reorderLayers(); + } + }; - map.on("styledata", handleStyleData); + map.on("styledata", handleStyleData); - return () => { - map.off("styledata", handleStyleData); - }; - }, [map, featureLayerIds, TMS_LAYER_ID, TILE_BOUNDARY_LAYER_ID]); -} + return () => { + map.off("styledata", handleStyleData); + }; + }, [map, featureLayerIds, TMS_LAYER_ID, TILE_BOUNDARY_LAYER_ID]); +}; diff --git a/frontend/src/layouts/root-layout.tsx b/frontend/src/layouts/root-layout.tsx index 9321958b..621a5a72 100644 --- a/frontend/src/layouts/root-layout.tsx +++ b/frontend/src/layouts/root-layout.tsx @@ -5,6 +5,7 @@ import { useEffect } from "react"; import { Banner } from "@/components/ui/banner"; import { APPLICATION_ROUTES } from "@/utils"; import { useScrollToTop } from "@/hooks/use-scroll-to-element"; +import { HotTracking } from "@/components/shared"; export const RootLayout = () => { const { pathname } = useLocation(); @@ -15,17 +16,24 @@ export const RootLayout = () => { }, [pathname]); return ( -
- - {!pathname.includes(APPLICATION_ROUTES.START_MAPPING_BASE) && } + <> + +
+ + {!pathname.includes(APPLICATION_ROUTES.START_MAPPING_BASE) && ( + + )} -
- -
- {!pathname.includes(APPLICATION_ROUTES.START_MAPPING_BASE) &&
} -
+
+ +
+ {!pathname.includes(APPLICATION_ROUTES.START_MAPPING_BASE) && ( +
+ )} +
+ ); }; diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 18a63fd7..ff772742 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -62,3 +62,8 @@ export type TFAQs = { question: string; answer: string; }[]; + +export type TNavBarLinks = { + title: string; + href: string; +}[]; From 8cf6a32134e66d0436504e769183250b68815e4f Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Thu, 26 Dec 2024 15:27:22 +0100 Subject: [PATCH 14/33] chore: fixed pointer events --- frontend/src/components/ui/drawer/mobile-drawer.tsx | 4 ++-- frontend/src/styles/index.css | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ui/drawer/mobile-drawer.tsx b/frontend/src/components/ui/drawer/mobile-drawer.tsx index 6ac6da54..09310b8d 100644 --- a/frontend/src/components/ui/drawer/mobile-drawer.tsx +++ b/frontend/src/components/ui/drawer/mobile-drawer.tsx @@ -34,7 +34,7 @@ export const MobileDrawer = ({ {canClose ? (
Date: Fri, 27 Dec 2024 22:02:35 +0100 Subject: [PATCH 15/33] chore:removed unnecessary memos --- .../src/app/providers/models-provider.tsx | 40 ++-- frontend/src/app/routes/account/models.tsx | 8 +- .../src/app/routes/models/models-list.tsx | 9 +- frontend/src/app/routes/start-mapping.tsx | 171 ++++++++--------- .../components/map/controls/layer-control.tsx | 29 ++- frontend/src/components/map/map.tsx | 100 ++++------ frontend/src/components/ui/banner/banner.tsx | 31 ++- frontend/src/components/ui/image/image.tsx | 1 + .../components/dialogs/file-upload-dialog.tsx | 62 +++--- .../components/progress-buttons.tsx | 12 +- .../training-area/training-area-item.tsx | 144 +++++++------- .../training-area/training-area-list.tsx | 37 ++-- .../training-dataset/create-new.tsx | 13 +- .../components/filters/status-filter.tsx | 11 +- .../components/maps/training-area-map.tsx | 92 ++++----- .../models/components/model-details-popup.tsx | 155 ++++++++------- .../src/features/models/hooks/use-models.ts | 7 +- .../components/logo-with-dropdown.tsx | 37 ++-- .../start-mapping/components/map/map.tsx | 37 +--- .../components/model-settings.tsx | 180 +++++++++--------- frontend/src/hooks/use-dropdown-menu.ts | 4 +- frontend/src/hooks/use-screen-size.ts | 5 +- 22 files changed, 531 insertions(+), 654 deletions(-) diff --git a/frontend/src/app/providers/models-provider.tsx b/frontend/src/app/providers/models-provider.tsx index 6e7ae18f..2b0a9881 100644 --- a/frontend/src/app/providers/models-provider.tsx +++ b/frontend/src/app/providers/models-provider.tsx @@ -223,8 +223,8 @@ const ModelsContext = createContext<{ validateEditMode: boolean; }>({ formData: initialFormState, - setFormData: () => {}, - handleChange: () => {}, + setFormData: () => { }, + handleChange: () => { }, createNewTrainingDatasetMutation: {} as UseMutationResult< TTrainingDataset, Error, @@ -239,13 +239,13 @@ const ModelsContext = createContext<{ >, hasLabeledTrainingAreas: false, hasAOIsWithGeometry: false, - resetState: () => {}, + resetState: () => { }, isEditMode: false, modelId: "", getFullPath: () => "", - handleModelCreationAndUpdate: () => {}, + handleModelCreationAndUpdate: () => { }, trainingDatasetCreationInProgress: false, - handleTrainingDatasetCreation: () => {}, + handleTrainingDatasetCreation: () => { }, validateEditMode: false, }); @@ -293,10 +293,7 @@ export const ModelsProvider: React.FC<{ ); // Will be used in the route validator component to delay the redirection for a while until the data are retrieved - const validateEditMode = useMemo( - () => formData.selectedTrainingDatasetId !== "" && formData.tmsURL !== "", - [formData], - ); + const validateEditMode = formData.selectedTrainingDatasetId !== "" && formData.tmsURL !== "" // Fetch and prefill model details useEffect(() => { @@ -421,22 +418,17 @@ export const ModelsProvider: React.FC<{ }); // Confirm that all the training areas labels has been retrieved - const hasLabeledTrainingAreas = useMemo(() => { - return ( - formData.trainingAreas.length > 0 && - formData.trainingAreas.filter( - (aoi: Feature) => aoi.properties.label_fetched === null, - ).length === 0 - ); - }, [formData]); + const hasLabeledTrainingAreas = formData.trainingAreas.length > 0 && + formData.trainingAreas.filter( + (aoi: Feature) => aoi.properties.label_fetched === null, + ).length === 0 + // Confirm that all of the training areas has a geometry - const hasAOIsWithGeometry = useMemo(() => { - return ( - formData.trainingAreas.length > 0 && - formData.trainingAreas.filter((aoi: Feature) => aoi.geometry === null) - .length === 0 - ); - }, [formData]); + const hasAOIsWithGeometry = formData.trainingAreas.length > 0 && + formData.trainingAreas.filter((aoi: Feature) => aoi.geometry === null) + .length === 0 + + const resetState = () => { setFormData(initialFormState); diff --git a/frontend/src/app/routes/account/models.tsx b/frontend/src/app/routes/account/models.tsx index 3988ce38..ad6af44f 100644 --- a/frontend/src/app/routes/account/models.tsx +++ b/frontend/src/app/routes/account/models.tsx @@ -19,7 +19,6 @@ import { } from "@/features/models/layouts"; import { useDialog } from "@/hooks/use-dialog"; import { APP_CONTENT } from "@/utils"; -import { useMemo } from "react"; import ModelNotFound from "@/features/models/components/model-not-found"; import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { useAuth } from "@/app/providers/auth-provider"; @@ -40,11 +39,6 @@ export const UserModelsPage = () => { updateQuery, } = useModelsListFilters(undefined, user?.osm_id); - // Since it's just a static filter, it's better to memoize it. - const memoizedCategoryFilter = useMemo( - () => , - [isPending], - ); const renderContent = () => { if (data?.count === 0) { @@ -92,7 +86,7 @@ export const UserModelsPage = () => {
- {memoizedCategoryFilter} + { isError: modelsMapDataIsError, } = useModelsMapData(); - // Since it's just a static filter, it's better to memoize it. - const memoizedCategoryFilter = useMemo( - () => , - [isPending], - ); // Mapview toggling interaction useEffect(() => { @@ -151,7 +146,7 @@ export const ModelsPage = () => {
- {memoizedCategoryFilter} + {/* Mobile filters */}
diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index 1d194ac5..efaab9c9 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { Head } from "@/components/seo"; import { BBOX, Feature, TileJSON, TModelPredictions } from "@/types"; @@ -114,13 +114,11 @@ export const StartMappingPage = () => { rejected: [], }); - const modelPredictionsExist = useMemo(() => { - return ( - modelPredictions.accepted.length > 0 || - modelPredictions.rejected.length > 0 || - modelPredictions.all.length > 0 - ); - }, [modelPredictions]); + const modelPredictionsExist = modelPredictions.accepted.length > 0 || + modelPredictions.rejected.length > 0 || + modelPredictions.all.length > 0 + + const updateQuery = useCallback( (newParams: TQueryParams) => { @@ -165,50 +163,48 @@ export const StartMappingPage = () => { const popupAnchorId = "model-details"; - const mapLayers = useMemo( - () => [ - ...(modelPredictions.accepted.length > 0 - ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .acceptedPredictions, - subLayers: [ - ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] - : []), - ...(modelPredictions.rejected.length > 0 - ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .rejectedPredictions, - subLayers: [ - REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] - : []), - ...(modelPredictions.all.length > 0 - ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .predictionResults, - subLayers: [ - ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, - ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] - : []), - ], - [modelPredictions, startMappingPageContent], - ); + const mapLayers = [ + ...(modelPredictions.accepted.length > 0 + ? [ + { + value: + startMappingPageContent.map.controls.legendControl + .acceptedPredictions, + subLayers: [ + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] + : []), + ...(modelPredictions.rejected.length > 0 + ? [ + { + value: + startMappingPageContent.map.controls.legendControl + .rejectedPredictions, + subLayers: [ + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] + : []), + ...(modelPredictions.all.length > 0 + ? [ + { + value: + startMappingPageContent.map.controls.legendControl + .predictionResults, + subLayers: [ + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] + : []), + ] + const handleAllFeaturesDownload = useCallback(async () => { geoJSONDowloader( @@ -255,48 +251,39 @@ export const StartMappingPage = () => { handleFeaturesDownloadToJOSM(modelPredictions.accepted); }, [handleFeaturesDownloadToJOSM, modelPredictions.accepted]); - const downloadOptions: TDownloadOptions = useMemo( - () => [ - { - name: startMappingPageContent.buttons.download.options.allFeatures, - value: startMappingPageContent.buttons.download.options.allFeatures, - onClick: handleAllFeaturesDownload, - showOnMobile: true, - }, - { - name: startMappingPageContent.buttons.download.options.acceptedFeatures, - value: - startMappingPageContent.buttons.download.options.acceptedFeatures, - onClick: handleAcceptedFeaturesDownload, - showOnMobile: true, - }, - { - name: startMappingPageContent.buttons.download.options + const downloadOptions: TDownloadOptions = [ + { + name: startMappingPageContent.buttons.download.options.allFeatures, + value: startMappingPageContent.buttons.download.options.allFeatures, + onClick: handleAllFeaturesDownload, + showOnMobile: true, + }, + { + name: startMappingPageContent.buttons.download.options.acceptedFeatures, + value: + startMappingPageContent.buttons.download.options.acceptedFeatures, + onClick: handleAcceptedFeaturesDownload, + showOnMobile: true, + }, + { + name: startMappingPageContent.buttons.download.options + .openAllFeaturesInJOSM, + value: + startMappingPageContent.buttons.download.options .openAllFeaturesInJOSM, - value: - startMappingPageContent.buttons.download.options - .openAllFeaturesInJOSM, - onClick: handleAllFeaturesDownloadToJOSM, - showOnMobile: false, - }, - { - name: startMappingPageContent.buttons.download.options + onClick: handleAllFeaturesDownloadToJOSM, + showOnMobile: false, + }, + { + name: startMappingPageContent.buttons.download.options + .openAcceptedFeaturesInJOSM, + value: + startMappingPageContent.buttons.download.options .openAcceptedFeaturesInJOSM, - value: - startMappingPageContent.buttons.download.options - .openAcceptedFeaturesInJOSM, - onClick: handleAcceptedFeaturesDownloadToJOSM, - showOnMobile: false, - }, - ], - [ - startMappingPageContent, - handleAcceptedFeaturesDownloadToJOSM, - handleAllFeaturesDownloadToJOSM, - handleAcceptedFeaturesDownload, - handleAllFeaturesDownload, - ], - ); + onClick: handleAcceptedFeaturesDownloadToJOSM, + showOnMobile: false, + }, + ] const handleModelDetailsPopup = useCallback(() => { setShowModelDetailsPopup((prev) => !prev); diff --git a/frontend/src/components/map/controls/layer-control.tsx b/frontend/src/components/map/controls/layer-control.tsx index cf6bc1f2..2b030870 100644 --- a/frontend/src/components/map/controls/layer-control.tsx +++ b/frontend/src/components/map/controls/layer-control.tsx @@ -2,7 +2,7 @@ import { LayerStackIcon } from "@/components/ui/icons"; import { DropDown } from "@/components/ui/dropdown"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { Map } from "maplibre-gl"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { CheckboxGroup } from "@/components/ui/form"; import { ToolTip } from "@/components/ui/tooltip"; import { BASEMAPS, ToolTipPlacement } from "@/enums"; @@ -14,7 +14,6 @@ import { import useScreenSize from "@/hooks/use-screen-size"; type TLayers = { id?: string; subLayers: string[]; value: string }[]; -type TBasemaps = { id?: string; subLayer: string; value: string }[]; export const LayerControl = ({ map, @@ -31,25 +30,23 @@ export const LayerControl = ({ useDropdownMenu(); const { isTablet, isMobile } = useScreenSize(); - const layerControlData = useMemo(() => { - const layers_ = [ + const layerControlData = { + layers_: [ ...layers, ...(openAerialMap ? [{ value: "TMS Layer", subLayers: [TMS_LAYER_ID] }] : []), - ]; - const baseLayers: TBasemaps = basemaps + ], + baseLayers: basemaps ? [ - { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, - { - value: BASEMAPS.GOOGLE_SATELLITE, - subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, - }, - ] - : []; - return { layers_, baseLayers }; - }, [layers, openAerialMap, basemaps]); - + { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, + { + value: BASEMAPS.GOOGLE_SATELLITE, + subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, + }, + ] + : [] + } const [layerVisibility, setLayerVisibility] = useState<{ [key: string]: boolean; }>({}); diff --git a/frontend/src/components/map/map.tsx b/frontend/src/components/map/map.tsx index bf368084..3b13602a 100644 --- a/frontend/src/components/map/map.tsx +++ b/frontend/src/components/map/map.tsx @@ -1,5 +1,5 @@ import "maplibre-gl/dist/maplibre-gl.css"; -import { RefObject, useMemo } from "react"; +import { RefObject } from "react"; import { DrawingModes } from "@/enums"; import { GeolocationControl, @@ -67,67 +67,51 @@ export const MapComponent: React.FC = ({ zoomControls = true, setDrawingMode, }) => { - const Controls = useMemo(() => { - if (!map) return; - return ( - <> -
+ {map ? + <> +
- {currentZoom && zoomControls ? ( - - ) : null} - {geolocationControl && } - {drawControl && terraDraw && drawingMode && setDrawingMode && ( - - )} -
- {map && fitToBounds && ( -
- + } map-elements-z-index flex flex-col gap-y-[1px]`} + > + {currentZoom && zoomControls ? ( + + ) : null} + {geolocationControl && } + {drawControl && terraDraw && drawingMode && setDrawingMode && ( + + )}
- )} -
- {showCurrentZoom && currentZoom ? ( - - ) : null} - {layerControl && ( - + {fitToBounds && ( +
+ +
)} -
- - ); - }, [ - map, - geolocationControl, - drawControl, - terraDraw, - controlsPosition, - layerControl, - showCurrentZoom, - currentZoom, - drawingMode && setDrawingMode, - fitToBounds, - ]); - - return ( -
- {Controls} +
+ {showCurrentZoom && currentZoom ? ( + + ) : null} + {layerControl && ( + + )} +
+ + : null + } {/* Order according to how they'll be rendered */} {basemaps && } {openAerialMap && oamTileJSONURL && ( diff --git a/frontend/src/components/ui/banner/banner.tsx b/frontend/src/components/ui/banner/banner.tsx index f8ef176c..23bdcb02 100644 --- a/frontend/src/components/ui/banner/banner.tsx +++ b/frontend/src/components/ui/banner/banner.tsx @@ -1,6 +1,6 @@ import { API_ENDPOINTS, apiClient } from "@/services"; import { useQuery } from "@tanstack/react-query"; -import { useMemo, useState } from "react"; +import { useState } from "react"; import Markdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -27,28 +27,21 @@ const Banner = () => { setIsBannerVisible(false); }; - const banner = useMemo( - () => ( -
- - {data?.[0]?.message} - - -
- ), - [data], - ); - if (!isBannerVisible || isError || data?.length === 0 || isLoading) { return null; } - return banner; + return
+ + {data?.[0]?.message} + + +
}; export default Banner; diff --git a/frontend/src/components/ui/image/image.tsx b/frontend/src/components/ui/image/image.tsx index 0bfc4bcb..ede55849 100644 --- a/frontend/src/components/ui/image/image.tsx +++ b/frontend/src/components/ui/image/image.tsx @@ -20,6 +20,7 @@ const Image: React.FC = ({ className, placeHolder, }) => { + const [isLoading, setIsLoading] = useState(true); const [imageSrc, setImageSrc] = useState(src); diff --git a/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx b/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx index caaefebd..a011c35d 100644 --- a/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx +++ b/frontend/src/features/model-creation/components/dialogs/file-upload-dialog.tsx @@ -13,7 +13,7 @@ import { validateGeoJSONArea, } from "@/utils"; import { SlFormatBytes } from "@shoelace-style/shoelace/dist/react"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useState } from "react"; import { FileWithPath, useDropzone } from "react-dropzone"; type FileUploadDialogProps = DialogProps & { @@ -181,40 +181,38 @@ const FileUploadDialog: React.FC = ({ } }; - const files = useMemo(() => { - return acceptedFiles.map((file) => { - return ( -
  • -
    -
    -
    - - - -
    -

    - {truncateString(file.file.name)} -

    - -
    + const files = acceptedFiles.map((file) => { + return ( +
  • +
    +
    +
    + + + +
    +

    + {truncateString(file.file.name)} +

    +
    -
    - {/* Removed ProgressBar */} +
    -
  • - ); - }); - }, [acceptedFiles, disabled, uploadInProgress]); + {/* Removed ProgressBar */} +
    + + ); + }); return ( = ({ if (currentPath.includes(MODELS_ROUTES.DETAILS)) { return ( formData.modelName.length >= - FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_NAME] - .minLength && + FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_NAME] + .minLength && formData.modelDescription.length >= - FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_DESCRIPTION] - .minLength + FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_DESCRIPTION] + .minLength ); } else if (currentPath.includes(MODELS_ROUTES.TRAINING_DATASET)) { // if the user hasn't selected any of the options, then they can not proceed to next page. @@ -118,8 +118,8 @@ const ProgressButtons: React.FC = ({ return ( formData.tmsURLValidation.valid && formData.datasetName.length >= - FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.DATASET_NAME] - .minLength + FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.DATASET_NAME] + .minLength ); } else if ( formData.trainingDatasetOption === TrainingDatasetOption.USE_EXISTING diff --git a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx index 97f8bffa..59d66a43 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx @@ -34,7 +34,7 @@ import { useGetTrainingAreaLabelsFromOSM, } from "@/features/model-creation/hooks/use-training-areas"; import { useModelsContext } from "@/app/providers/models-provider"; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { memo, useCallback, useEffect, useRef, useState } from "react"; import FileUploadDialog from "@/features/model-creation/components/dialogs/file-upload-dialog"; import { useDialog } from "@/hooks/use-dialog"; import { geojsonToWKT } from "@terraformer/wkt"; @@ -136,7 +136,7 @@ const TrainingAreaItem: React.FC< if ( getTrainingArea.isError || getTrainingArea.data?.properties.label_status === - LabelStatus.NOT_DOWNLOADED + LabelStatus.NOT_DOWNLOADED ) { handleLabelError(); } else if ( @@ -272,83 +272,77 @@ const TrainingAreaItem: React.FC< }); }; - const dropdownMenuItems = useMemo( - () => [ - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openINJOSM, - isIcon: false, - imageSrc: JOSMLogo, - onClick: () => - openInJOSM(formData.oamTileName, formData.tmsURL, [trainingArea]), - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openInIdEditor, - isIcon: false, - imageSrc: OSMLogo, - onClick: () => - openInIDEditor( - formData.oamBounds[1], - formData.oamBounds[3], - formData.oamBounds[0], - formData.oamBounds[2], - formData.tmsURL, - formData.selectedTrainingDatasetId, - trainingArea.id, - ), + const dropdownMenuItems = [ + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openINJOSM, + isIcon: false, + imageSrc: JOSMLogo, + onClick: () => + openInJOSM(formData.oamTileName, formData.tmsURL, [trainingArea]), + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.openInIdEditor, + isIcon: false, + imageSrc: OSMLogo, + onClick: () => + openInIDEditor( + formData.oamBounds[1], + formData.oamBounds[3], + formData.oamBounds[0], + formData.oamBounds[2], + formData.tmsURL, + formData.selectedTrainingDatasetId, + trainingArea.id, + ), + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadAOI, + isIcon: true, + Icon: CloudDownloadIcon, + onClick: () => { + geoJSONDowloader(trainingArea, `AOI_${trainingArea.id}`); + showSuccessToast(TOAST_NOTIFICATIONS.aoiDownloadSuccess); + onDropdownHide(); }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadAOI, - isIcon: true, - Icon: CloudDownloadIcon, - onClick: () => { - geoJSONDowloader(trainingArea, `AOI_${trainingArea.id}`); - showSuccessToast(TOAST_NOTIFICATIONS.aoiDownloadSuccess); + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadLabels, + isIcon: true, + Icon: CloudDownloadIcon, + onClick: async () => { + const res = await getTrainingAreaLabels.refetch(); + if (res.isSuccess) { + geoJSONDowloader(res.data, `AOI_${trainingArea.id}_Labels`); onDropdownHide(); - }, - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.downloadLabels, - isIcon: true, - Icon: CloudDownloadIcon, - onClick: async () => { - const res = await getTrainingAreaLabels.refetch(); - if (res.isSuccess) { - geoJSONDowloader(res.data, `AOI_${trainingArea.id}_Labels`); - onDropdownHide(); - showSuccessToast(TOAST_NOTIFICATIONS.aoiLabelsDownloadSuccess); - } - if (res.isError) showErrorToast(res.error); - }, + showSuccessToast(TOAST_NOTIFICATIONS.aoiLabelsDownloadSuccess); + } + if (res.isError) showErrorToast(res.error); }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.uploadLabels, - isIcon: true, - Icon: UploadIcon, - onClick: openDialog, - }, - { - tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.deleteAOI, - isIcon: true, - Icon: DeleteIcon, - isDelete: true, - onClick: () => - deleteTrainingAreaMutation.mutate({ - trainingAreaId: trainingArea.id, - }), - }, - ], - [formData, trainingArea, onDropdownHide, getTrainingAreaLabels], - ); + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.uploadLabels, + isIcon: true, + Icon: UploadIcon, + onClick: openDialog, + }, + { + tooltip: MODEL_CREATION_CONTENT.trainingArea.toolTips.deleteAOI, + isIcon: true, + Icon: DeleteIcon, + isDelete: true, + onClick: () => + deleteTrainingAreaMutation.mutate({ + trainingAreaId: trainingArea.id, + }), + }, + ] - const trainingAreaSize = useMemo( - () => - trainingArea.geometry - ? formatAreaInAppropriateUnit(calculateGeoJSONArea(trainingArea)) - : "0 m²", - [trainingArea], - ); - const fetchStatusInfo = useMemo(() => { + const trainingAreaSize = trainingArea.geometry + ? formatAreaInAppropriateUnit(calculateGeoJSONArea(trainingArea)) + : "0 m²" + + const fetchStatusInfo = () => { if (labelState.isFetching || trainingAreaLabelsMutation.isPending) { return "Fetching labels..."; } @@ -361,7 +355,7 @@ const TrainingAreaItem: React.FC< : "Fetched recently"; } return "No labels yet"; - }, [labelState, trainingAreaLabelsMutation.isPending]); + } return ( <> diff --git a/frontend/src/features/model-creation/components/training-area/training-area-list.tsx b/frontend/src/features/model-creation/components/training-area/training-area-list.tsx index dfed0ca8..fa55244b 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-list.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-list.tsx @@ -1,7 +1,7 @@ import TrainingAreaItem from "@/features/model-creation/components/training-area/training-area-item"; import { Pagination } from "@/components/shared"; import { PaginatedTrainingArea } from "@/types"; -import { Dispatch, SetStateAction, useMemo } from "react"; +import { Dispatch, SetStateAction } from "react"; import { formatDuration, MODEL_CREATION_CONTENT, @@ -39,25 +39,6 @@ const TrainingAreaList = ({ refetchInterval: OSM_LAST_UPDATED_POOLING_INTERVAL_MS, }); - const OSMLastUpdated = useMemo(() => { - return ( - - {isOSMPending || isOSMError ? ( - "" - ) : ( - - {MODEL_CREATION_CONTENT.trainingArea.toolTips.lastUpdatedPrefix}{" "} - {formatDuration( - new Date(String(osmData?.lastUpdated)), - new Date(), - 1, - )}{" "} - ago - - )} - - ); - }, [isOSMPending, isOSMError, osmData]); return (
    @@ -69,7 +50,21 @@ const TrainingAreaList = ({ {data?.count ?? 0}

    - {OSMLastUpdated} + + {isOSMPending || isOSMError ? ( + "" + ) : ( + + {MODEL_CREATION_CONTENT.trainingArea.toolTips.lastUpdatedPrefix}{" "} + {formatDuration( + new Date(String(osmData?.lastUpdated)), + new Date(), + 1, + )}{" "} + ago + + )} +
    { const { formData, handleChange } = useModelsContext(); - const tmsURLHelpText = useMemo(() => { - const helpText = - MODEL_CREATION_CONTENT.trainingDataset.form.tmsURL.helpText; - return formData.tmsURLValidation.message.length > 0 - ? formData.tmsURLValidation.message - : helpText; - }, [formData.tmsURLValidation.message]); + const tmsURLHelpText = formData.tmsURLValidation.message.length > 0 + ? formData.tmsURLValidation.message + : MODEL_CREATION_CONTENT.trainingDataset.form.tmsURL.helpText; + useEffect(() => { // Shoelace will handle the validation when it's more than 0 characters. diff --git a/frontend/src/features/models/components/filters/status-filter.tsx b/frontend/src/features/models/components/filters/status-filter.tsx index 3f7d0054..4ad32dfe 100644 --- a/frontend/src/features/models/components/filters/status-filter.tsx +++ b/frontend/src/features/models/components/filters/status-filter.tsx @@ -4,7 +4,6 @@ import { DropdownMenuItem } from "@/components/ui/dropdown/dropdown"; import { CheckboxGroup } from "@/components/ui/form"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { TQueryParams } from "@/types"; -import { useMemo } from "react"; type StatusFilterProps = { disabled: boolean; @@ -57,13 +56,9 @@ const StatusFilter: React.FC = ({ }, }, ]; - const categoryLabel = useMemo( - () => - statusCategories.filter( - (status) => status.apiValue === query[SEARCH_PARAMS.status], - ), - [query], - ); + const categoryLabel = statusCategories.filter( + (status) => status.apiValue === query[SEARCH_PARAMS.status], + ) const { dropdownIsOpened, onDropdownHide, onDropdownShow } = useDropdownMenu(); diff --git a/frontend/src/features/models/components/maps/training-area-map.tsx b/frontend/src/features/models/components/maps/training-area-map.tsx index b9a23f23..b508f1d9 100644 --- a/frontend/src/features/models/components/maps/training-area-map.tsx +++ b/frontend/src/features/models/components/maps/training-area-map.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useCallback, useMemo, useRef } from "react"; +import { useEffect, useState, useCallback, useRef } from "react"; import { MapComponent } from "@/components/map"; import { ControlsPosition } from "@/enums"; import { PMTiles } from "pmtiles"; @@ -81,56 +81,46 @@ export const TrainingAreaMap = ({ const tileJSONURL = extractTileJSONURL(tmsURL); - const trainingAreasSourceId = useMemo( - () => `training-areas-for-${trainingAreaId}`, - [trainingAreaId], - ); + const trainingAreasSourceId = `training-areas-for-${trainingAreaId}` - const mapLayers: LayerSpecification[] = useMemo(() => { - return vectorLayers.flatMap((layer) => { - const { fill, outline } = getLayerConfigs(layer.id); - return [ - { - id: `${layer.id}_fill`, - type: "fill", - source: trainingAreasSourceId, - paint: fill, - "source-layer": layer.id, - layout: { visibility: "visible" }, - }, - { - id: `${layer.id}_outline`, - type: "line", - source: trainingAreasSourceId, - paint: outline, - "source-layer": layer.id, - layout: { visibility: "visible" }, - }, - ]; - }); - }, [vectorLayers, trainingAreasSourceId]); - - const sources = useMemo( - () => [ + const mapLayers: LayerSpecification[] = vectorLayers.flatMap((layer) => { + const { fill, outline } = getLayerConfigs(layer.id); + return [ { - id: trainingAreasSourceId, - spec: { - type: "vector", - url: `pmtiles://${file}`, - } as SourceSpecification, + id: `${layer.id}_fill`, + type: "fill", + source: trainingAreasSourceId, + paint: fill, + "source-layer": layer.id, + layout: { visibility: "visible" }, }, - ], - [file, trainingAreasSourceId], - ); + { + id: `${layer.id}_outline`, + type: "line", + source: trainingAreasSourceId, + paint: outline, + "source-layer": layer.id, + layout: { visibility: "visible" }, + }, + ]; + }); + + + const sources = [ + { + id: trainingAreasSourceId, + spec: { + type: "vector", + url: `pmtiles://${file}`, + } as SourceSpecification, + }, + ] + + const layerControlLayers = vectorLayers.map((layer) => ({ + value: `Training ${layer.id}`, + subLayers: [`${layer.id}_fill`, `${layer.id}_outline`], + })) - const layerControlLayers = useMemo( - () => - vectorLayers.map((layer) => ({ - value: `Training ${layer.id}`, - subLayers: [`${layer.id}_fill`, `${layer.id}_outline`], - })), - [vectorLayers], - ); const fitToBounds = useCallback(() => { if ( @@ -182,15 +172,15 @@ export const TrainingAreaMap = ({ ${Object.entries(feature.properties) - .map( - ([key, value]) => ` + .map( + ([key, value]) => ` `, - ) - .join("")} + ) + .join("")}
    ${key} ${typeof value === "boolean" ? JSON.stringify(value) : value}
    diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index baca322d..50bb585e 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -6,7 +6,6 @@ import { TModelDetails, TTrainingDataset } from "@/types"; import { useTrainingDetails } from "@/features/models/hooks/use-training"; import { startMappingPageContent } from "@/constants"; import useScreenSize from "@/hooks/use-screen-size"; -import { useMemo } from "react"; import { MobileDrawer } from "@/components/ui/drawer"; const ModelDetailsPopUp = ({ @@ -44,87 +43,83 @@ const ModelDetailsPopUp = ({ ); const { isSmallViewport } = useScreenSize(); - const popupContent = useMemo(() => { - return ( - -
    -

    - {startMappingPageContent.modelDetails.popover.modelId}:{" "} - {model?.id ?? data?.id} -

    -

    - {startMappingPageContent.modelDetails.popover.description}:{" "} - - {model?.description ?? data?.description} - -

    -

    - {startMappingPageContent.modelDetails.popover.lastModified}:{" "} - - {extractDatePart( - model?.last_modified ?? (data?.last_modified as string), - )} - -

    -

    - {startMappingPageContent.modelDetails.popover.trainingId}:{" "} - - {model?.published_training ?? data?.published_training} - -

    -

    - {startMappingPageContent.modelDetails.popover.datasetId}:{" "} - - {model?.dataset ?? data?.dataset} - -

    -

    - {startMappingPageContent.modelDetails.popover.datasetName}:{" "} - - - {trainingDatasetIsError - ? "N/A" - : truncateString(trainingDataset?.name, 40)}{" "} - - -

    + const popupContent = +
    +

    + {startMappingPageContent.modelDetails.popover.modelId}:{" "} + {model?.id ?? data?.id} +

    +

    + {startMappingPageContent.modelDetails.popover.description}:{" "} + + {model?.description ?? data?.description} + +

    +

    + {startMappingPageContent.modelDetails.popover.lastModified}:{" "} + + {extractDatePart( + model?.last_modified ?? (data?.last_modified as string), + )} + +

    +

    + {startMappingPageContent.modelDetails.popover.trainingId}:{" "} + + {model?.published_training ?? data?.published_training} + +

    +

    + {startMappingPageContent.modelDetails.popover.datasetId}:{" "} + + {model?.dataset ?? data?.dataset} + +

    +

    + {startMappingPageContent.modelDetails.popover.datasetName}:{" "} + + + {trainingDatasetIsError + ? "N/A" + : truncateString(trainingDataset?.name, 40)}{" "} + + +

    -

    - {startMappingPageContent.modelDetails.popover.zoomLevel}:{" "} - - - {trainingDetailsError - ? "N/A" - : trainingDetails?.zoom_level?.join(", ")}{" "} - - -

    +

    + {startMappingPageContent.modelDetails.popover.zoomLevel}:{" "} + + + {trainingDetailsError + ? "N/A" + : trainingDetails?.zoom_level?.join(", ")}{" "} + + +

    -

    - {startMappingPageContent.modelDetails.popover.accuracy}:{" "} - - {roundNumber(model?.accuracy ?? (data?.accuracy as number), 2)}% - -

    -

    - {startMappingPageContent.modelDetails.popover.baseModel}:{" "} - - {model?.base_model ?? data?.base_model} - -

    -
    -
    - ); - }, [modelId, isPending, model, data, isError]); +

    + {startMappingPageContent.modelDetails.popover.accuracy}:{" "} + + {roundNumber(model?.accuracy ?? (data?.accuracy as number), 2)}% + +

    +

    + {startMappingPageContent.modelDetails.popover.baseModel}:{" "} + + {model?.base_model ?? data?.base_model} + +

    +
    +
    if (isSmallViewport) { return ( diff --git a/frontend/src/features/models/hooks/use-models.ts b/frontend/src/features/models/hooks/use-models.ts index 0d58d627..aae63e2d 100644 --- a/frontend/src/features/models/hooks/use-models.ts +++ b/frontend/src/features/models/hooks/use-models.ts @@ -8,7 +8,7 @@ import { useSearchParams } from "react-router-dom"; import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { ORDERING_FIELDS } from "@/features/models/components/filters/ordering-filter"; import { TQueryParams } from "@/types"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { buildDateFilterQueryString } from "@/utils"; import { PAGE_LIMIT } from "@/components/shared"; import { dateFilters } from "@/features/models/components/filters/date-range-filter"; @@ -177,10 +177,7 @@ export const useModelsListFilters = ( setQuery(newQuery); }, []); - const mapViewIsActive = useMemo( - () => query[SEARCH_PARAMS.mapIsActive], - [query], - ); + const mapViewIsActive = query[SEARCH_PARAMS.mapIsActive] const clearAllFilters = useCallback(() => { const resetParams = new URLSearchParams(); diff --git a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx index 67d13b44..1e39c43e 100644 --- a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx +++ b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from "react"; +import { memo } from "react"; import { DropDown } from "@/components/ui/dropdown"; import { NavLogo } from "@/components/layout"; import { Divider } from "@/components/ui/divider"; @@ -20,25 +20,22 @@ export const BrandLogoWithDropDown = memo(function BrandLogoWithDropDown({ onShow, }: BrandLogoWithDropDownProps) { - const navItems = useMemo( - () => - navLinks.map((link, id) => ( -
  • - - {link.title} - -
  • - )), - [], - ); + const navItems = navLinks.map((link, id) => ( +
  • + + {link.title} + +
  • + )) + const navigate = useNavigate() return ( ( - - ), - [ - showPopup, - selectedEvent, - selectedFeature, - setModelPredictions, - modelPredictions, - trainingDataset?.source_imagery, - trainingDataset?.id, - trainingConfig, - map, - ], - ); const showTooltip = currentZoom < MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION && tooltipVisible; return ( @@ -311,7 +285,16 @@ export const StartMappingMapComponent = ({ basemaps showCurrentZoom={!isSmallViewport} > - {showPopup && renderPopup} + {showPopup && } { - return ( -
    -
    - - { - handleQueryUpdate(SEARCH_PARAMS.useJOSMQ, event.target.checked); - }} - /> -
    -
    - - - handleQueryUpdate( - SEARCH_PARAMS.tolerance, - Number(event.target.value), - ) - } - min={0} - step={0.1} - /> -
    -
    - - - handleQueryUpdate( - SEARCH_PARAMS.area, - Number(event.target.value), - ) - } - min={0} - /> -
    -
    - ); - }, [query, handleQueryUpdate, SEARCH_PARAMS]); + const modelSettings =
    +
    + + { + handleQueryUpdate(SEARCH_PARAMS.useJOSMQ, event.target.checked); + }} + /> +
    +
    + + + handleQueryUpdate( + SEARCH_PARAMS.tolerance, + Number(event.target.value), + ) + } + min={0} + step={0.1} + /> +
    +
    + + + handleQueryUpdate( + SEARCH_PARAMS.area, + Number(event.target.value), + ) + } + min={0} + /> +
    +
    if (!isMobile) { return ( diff --git a/frontend/src/hooks/use-dropdown-menu.ts b/frontend/src/hooks/use-dropdown-menu.ts index be835d6e..9a5dc1aa 100644 --- a/frontend/src/hooks/use-dropdown-menu.ts +++ b/frontend/src/hooks/use-dropdown-menu.ts @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useState } from "react"; /** * Custom hook to manage the visibility state of a dropdown menu. @@ -30,6 +30,6 @@ export const useDropdownMenu = () => { } }, [isOpened]); - const dropdownIsOpened = useMemo(() => isOpened, [isOpened]); + const dropdownIsOpened = isOpened return { onDropdownHide, onDropdownShow, dropdownIsOpened, toggleDropDown }; }; diff --git a/frontend/src/hooks/use-screen-size.ts b/frontend/src/hooks/use-screen-size.ts index 51c3a12f..f41e27f5 100644 --- a/frontend/src/hooks/use-screen-size.ts +++ b/frontend/src/hooks/use-screen-size.ts @@ -37,10 +37,7 @@ const useScreenSize = () => { }; }, []); - const isSmallViewport = useMemo( - () => screenSize.isMobile || screenSize.isTablet, - [screenSize], - ); + const isSmallViewport = screenSize.isMobile || screenSize.isTablet return { ...screenSize, isSmallViewport }; }; From 51a8193917f6bc6a8e2d7e6e04b0f8c212bfa58a Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Fri, 27 Dec 2024 22:17:20 +0100 Subject: [PATCH 16/33] chore:removed unnecessary memos --- frontend/src/components/shared/faqs/faqs.tsx | 5 +- .../src/components/shared/section-header.tsx | 6 +- .../shared/static-page-header/header.tsx | 10 +- .../src/components/ui/dropdown/dropdown.tsx | 1 - .../components/progress-bar.tsx | 126 +++++++++--------- .../training-area/training-area-item.tsx | 6 +- .../components/logo-with-dropdown.tsx | 5 +- .../components/model-settings.tsx | 8 +- 8 files changed, 79 insertions(+), 88 deletions(-) diff --git a/frontend/src/components/shared/faqs/faqs.tsx b/frontend/src/components/shared/faqs/faqs.tsx index db53b9a1..264b4606 100644 --- a/frontend/src/components/shared/faqs/faqs.tsx +++ b/frontend/src/components/shared/faqs/faqs.tsx @@ -4,10 +4,9 @@ import { Accordion } from "@/components/ui/accordion"; import { Link } from "@/components/ui/link"; import { APPLICATION_ROUTES } from "@/utils"; import { TFAQs } from "@/types"; -import React from "react"; import { ChevronDownIcon } from "@/components/ui/icons"; -export const FAQs = React.memo( +export const FAQs = ({ faqs = APP_CONTENT.homepage.faqs.content, disableSeeMoreButton, @@ -47,4 +46,4 @@ export const FAQs = React.memo( ); }, -); + diff --git a/frontend/src/components/shared/section-header.tsx b/frontend/src/components/shared/section-header.tsx index 7a768cfa..13cf202c 100644 --- a/frontend/src/components/shared/section-header.tsx +++ b/frontend/src/components/shared/section-header.tsx @@ -1,9 +1,7 @@ -import React from "react"; - -export const SectionHeader = React.memo(({ title }: { title: string }) => { +export const SectionHeader = ({ title }: { title: string }) => { return (

    {title}

    ); -}); +}; diff --git a/frontend/src/components/shared/static-page-header/header.tsx b/frontend/src/components/shared/static-page-header/header.tsx index 436e7fcc..4e9ef817 100644 --- a/frontend/src/components/shared/static-page-header/header.tsx +++ b/frontend/src/components/shared/static-page-header/header.tsx @@ -1,7 +1,7 @@ -import React from "react"; + import styles from "./header.module.css"; -export const Header = React.memo(({ title }: { title: string }) => { +export const Header = ({ title }: { title: string }) => { return (
    {
    ); -}); +}; -const Rectangles = React.memo(() => { +const Rectangles = () => { return (
    @@ -26,4 +26,4 @@ const Rectangles = React.memo(() => {
    ); -}); +}; diff --git a/frontend/src/components/ui/dropdown/dropdown.tsx b/frontend/src/components/ui/dropdown/dropdown.tsx index d7857938..1fbeb57c 100644 --- a/frontend/src/components/ui/dropdown/dropdown.tsx +++ b/frontend/src/components/ui/dropdown/dropdown.tsx @@ -51,7 +51,6 @@ const DropDown: React.FC = ({ defaultSelectedItems = [], defaultSelectedItem = "", multiSelect = false, - triggerComponent, distance = 20, disableCheveronIcon = false, diff --git a/frontend/src/features/model-creation/components/progress-bar.tsx b/frontend/src/features/model-creation/components/progress-bar.tsx index 9d128fa3..7bf7aa37 100644 --- a/frontend/src/features/model-creation/components/progress-bar.tsx +++ b/frontend/src/features/model-creation/components/progress-bar.tsx @@ -1,7 +1,7 @@ import { useModelsContext } from "@/app/providers/models-provider"; import { CheckIcon } from "@/components/ui/icons"; import { cn } from "@/utils"; -import { memo, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; import { useNavigate } from "react-router-dom"; type ProgressBarProps = { @@ -10,72 +10,68 @@ type ProgressBarProps = { pages: { id: number; title: string; icon: React.ElementType; path: string }[]; }; -const ProgressBar: React.FC = memo( - ({ currentPath, currentPageIndex, pages }) => { - const navigate = useNavigate(); - const { getFullPath, isEditMode } = useModelsContext(); - const activeStepRef = useRef(null); - const containerRef = useRef(null); +const ProgressBar: React.FC = ({ currentPath, currentPageIndex, pages }) => { + const navigate = useNavigate(); + const { getFullPath, isEditMode } = useModelsContext(); + const activeStepRef = useRef(null); + const containerRef = useRef(null); - useEffect(() => { - if (activeStepRef.current && containerRef.current) { - const container = containerRef.current; - const activeStep = activeStepRef.current; + useEffect(() => { + if (activeStepRef.current && containerRef.current) { + const container = containerRef.current; + const activeStep = activeStepRef.current; - const offset = - activeStep.offsetLeft - - container.offsetWidth / 2 + - activeStep.offsetWidth / 2; - container.scrollTo({ - left: offset, - behavior: "smooth", - }); - } - }, [currentPath]); + const offset = + activeStep.offsetLeft - + container.offsetWidth / 2 + + activeStep.offsetWidth / 2; + container.scrollTo({ + left: offset, + behavior: "smooth", + }); + } + }, [currentPath]); - return ( -
    - {pages.map((step, index) => { - const activeStep = currentPath.includes(step.path); - const isLastPage = index === pages.length - 1; - return ( - - ); - })} -
    - ); - }, -); + return ( +
    + {pages.map((step, index) => { + const activeStep = currentPath.includes(step.path); + const isLastPage = index === pages.length - 1; + return ( + + ); + })} +
    + ); +}; export default ProgressBar; diff --git a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx index 59d66a43..c87498b0 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx @@ -34,7 +34,7 @@ import { useGetTrainingAreaLabelsFromOSM, } from "@/features/model-creation/hooks/use-training-areas"; import { useModelsContext } from "@/app/providers/models-provider"; -import { memo, useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import FileUploadDialog from "@/features/model-creation/components/dialogs/file-upload-dialog"; import { useDialog } from "@/hooks/use-dialog"; import { geojsonToWKT } from "@terraformer/wkt"; @@ -72,7 +72,7 @@ const TrainingAreaItem: React.FC< offset: number; map: Map | null; } -> = memo(({ datasetId, offset, map, ...trainingArea }) => { +> = ({ datasetId, offset, map, ...trainingArea }) => { const initialLabelState: LabelState = { isFetching: false, error: false, @@ -447,6 +447,6 @@ const TrainingAreaItem: React.FC<
    ); -}); +}; export default TrainingAreaItem; diff --git a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx index 1e39c43e..0f48ad2f 100644 --- a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx +++ b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx @@ -1,4 +1,3 @@ -import { memo } from "react"; import { DropDown } from "@/components/ui/dropdown"; import { NavLogo } from "@/components/layout"; import { Divider } from "@/components/ui/divider"; @@ -14,7 +13,7 @@ type BrandLogoWithDropDownProps = { } -export const BrandLogoWithDropDown = memo(function BrandLogoWithDropDown({ +export const BrandLogoWithDropDown = function BrandLogoWithDropDown({ isOpened, onClose, onShow, @@ -63,4 +62,4 @@ export const BrandLogoWithDropDown = memo(function BrandLogoWithDropDown({
    ); -}); +}; diff --git a/frontend/src/features/start-mapping/components/model-settings.tsx b/frontend/src/features/start-mapping/components/model-settings.tsx index 8122fcea..7d4e30e9 100644 --- a/frontend/src/features/start-mapping/components/model-settings.tsx +++ b/frontend/src/features/start-mapping/components/model-settings.tsx @@ -6,7 +6,7 @@ import { ToolTip } from "@/components/ui/tooltip"; import { startMappingPageContent } from "@/constants"; import { DropdownPlacement, INPUT_TYPES, SHOELACE_SIZES } from "@/enums"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; -import { memo } from "react"; + const confidenceLevels = [ { @@ -27,7 +27,7 @@ const confidenceLevels = [ }, ]; -export const ModelSettings = memo( +export const ModelSettings = ({ query, updateQuery, @@ -166,5 +166,5 @@ export const ModelSettings = memo( ); } return modelSettings; - }, -); + } + ; From 5f47a9ccb9c0122dbb2a7e58dd3d23fce494b621 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Fri, 27 Dec 2024 22:55:54 +0100 Subject: [PATCH 17/33] chore: cleaning up bad typings --- frontend/src/app/index.tsx | 7 ++++ .../src/app/routes/models/model-details.tsx | 14 +++---- .../components/layout/navbar/user-profile.tsx | 4 +- .../components/map/controls/layer-control.tsx | 19 +++++---- frontend/src/components/shared/faqs/faqs.tsx | 2 +- .../src/components/ui/dropdown/dropdown.tsx | 3 +- .../ui/form/checkbox-group/checkbox-group.tsx | 2 +- .../training-area/training-area-item.tsx | 2 +- .../training-area/training-area-map.tsx | 8 ++-- .../model-creation/hooks/use-tms-tilejson.ts | 3 +- .../hooks/use-training-areas.ts | 8 ---- .../hooks/use-training-datasets.ts | 2 - .../models/components/directory-tree.tsx | 40 +++++++++---------- .../src/features/models/hooks/use-dataset.ts | 2 - .../src/features/models/hooks/use-models.ts | 6 --- .../src/features/models/hooks/use-training.ts | 10 ----- frontend/src/types/common.ts | 9 ++++- frontend/src/utils/error-handler.ts | 24 +++++++++++ frontend/src/utils/index.ts | 1 + 19 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 frontend/src/utils/error-handler.ts diff --git a/frontend/src/app/index.tsx b/frontend/src/app/index.tsx index da83bedb..679d2d0a 100644 --- a/frontend/src/app/index.tsx +++ b/frontend/src/app/index.tsx @@ -6,6 +6,7 @@ import { } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { showErrorToast } from "@/utils"; +import axios from "axios"; export const App = () => { const queryClient = new QueryClient({ @@ -16,6 +17,12 @@ export const App = () => { if (query.state.data !== undefined) { showErrorToast(error); } + if (axios.isAxiosError(error)) { + // Server errors + if (error.response?.status && error.response.status >= 500) { + throw error; + } + } }, }), }); diff --git a/frontend/src/app/routes/models/model-details.tsx b/frontend/src/app/routes/models/model-details.tsx index 2eac802b..18a9eb9b 100644 --- a/frontend/src/app/routes/models/model-details.tsx +++ b/frontend/src/app/routes/models/model-details.tsx @@ -11,7 +11,7 @@ import { ModelFilesDialog } from "@/features/models/components/dialogs"; import { ModelDetailsSkeleton } from "@/features/models/components/skeletons"; import { useModelDetails } from "@/features/models/hooks/use-models"; import { useDialog } from "@/hooks/use-dialog"; -import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; +import { APP_CONTENT, } from "@/utils"; import { useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Image } from "@/components/ui/image"; @@ -22,6 +22,8 @@ import { TrainingAreaDrawer } from "@/features/models/components/training-area-d import { useGetTrainingDataset } from "@/features/models/hooks/use-dataset"; import { TrainingInProgressImage } from "@/assets/images"; +import { handleErrorNavigation } from "@/utils"; + export const ModelDetailsPage = () => { const { id } = useParams<{ id: string }>(); @@ -41,14 +43,8 @@ export const ModelDetailsPage = () => { const { isAuthenticated } = useAuth(); useEffect(() => { - if (isError) { - navigate(APPLICATION_ROUTES.NOTFOUND, { - state: { - from: window.location.pathname, - //@ts-expect-error bad type definition - error: error?.response?.data?.detail, - }, - }); + if (isError && error) { + handleErrorNavigation(error, navigate); } }, [isError, error, navigate]); diff --git a/frontend/src/components/layout/navbar/user-profile.tsx b/frontend/src/components/layout/navbar/user-profile.tsx index 927faf9e..374332b4 100644 --- a/frontend/src/components/layout/navbar/user-profile.tsx +++ b/frontend/src/components/layout/navbar/user-profile.tsx @@ -7,6 +7,7 @@ import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { useAuth } from "@/app/providers/auth-provider"; import useScreenSize from "@/hooks/use-screen-size"; import { DropdownPlacement } from "@/enums"; +import { TCSSWithVars } from "@/types"; export const UserProfile = ({ hideFullName, @@ -60,8 +61,7 @@ export const UserProfile = ({ label={user?.username} loading="lazy" initials={user?.username.charAt(0)} - // @ts-expect-error bad type definition - style={{ "--size": size }} + style={{ "--size": size } as TCSSWithVars} /> {!hideFullName && (

    diff --git a/frontend/src/components/map/controls/layer-control.tsx b/frontend/src/components/map/controls/layer-control.tsx index 2b030870..81e902af 100644 --- a/frontend/src/components/map/controls/layer-control.tsx +++ b/frontend/src/components/map/controls/layer-control.tsx @@ -2,7 +2,7 @@ import { LayerStackIcon } from "@/components/ui/icons"; import { DropDown } from "@/components/ui/dropdown"; import { useDropdownMenu } from "@/hooks/use-dropdown-menu"; import { Map } from "maplibre-gl"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { CheckboxGroup } from "@/components/ui/form"; import { ToolTip } from "@/components/ui/tooltip"; import { BASEMAPS, ToolTipPlacement } from "@/enums"; @@ -14,6 +14,7 @@ import { import useScreenSize from "@/hooks/use-screen-size"; type TLayers = { id?: string; subLayers: string[]; value: string }[]; +type TBasemaps = { id?: string; subLayer: string; value: string }[]; export const LayerControl = ({ map, @@ -30,14 +31,14 @@ export const LayerControl = ({ useDropdownMenu(); const { isTablet, isMobile } = useScreenSize(); - const layerControlData = { - layers_: [ + const layerControlData = useMemo(() => { + const layers_ = [ ...layers, ...(openAerialMap ? [{ value: "TMS Layer", subLayers: [TMS_LAYER_ID] }] : []), - ], - baseLayers: basemaps + ]; + const baseLayers: TBasemaps = basemaps ? [ { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, { @@ -45,8 +46,10 @@ export const LayerControl = ({ subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, }, ] - : [] - } + : []; + return { layers_, baseLayers }; + }, [layers, openAerialMap, basemaps]); + const [layerVisibility, setLayerVisibility] = useState<{ [key: string]: boolean; }>({}); @@ -173,4 +176,4 @@ export const LayerControl = ({ ); -}; +}; \ No newline at end of file diff --git a/frontend/src/components/shared/faqs/faqs.tsx b/frontend/src/components/shared/faqs/faqs.tsx index 264b4606..176fd784 100644 --- a/frontend/src/components/shared/faqs/faqs.tsx +++ b/frontend/src/components/shared/faqs/faqs.tsx @@ -45,5 +45,5 @@ export const FAQs =

    ); - }, + } diff --git a/frontend/src/components/ui/dropdown/dropdown.tsx b/frontend/src/components/ui/dropdown/dropdown.tsx index 1fbeb57c..a228b03e 100644 --- a/frontend/src/components/ui/dropdown/dropdown.tsx +++ b/frontend/src/components/ui/dropdown/dropdown.tsx @@ -51,6 +51,7 @@ const DropDown: React.FC = ({ defaultSelectedItems = [], defaultSelectedItem = "", multiSelect = false, + triggerComponent, distance = 20, disableCheveronIcon = false, @@ -159,4 +160,4 @@ const DropDown: React.FC = ({ ); }; -export default DropDown; +export default DropDown; \ No newline at end of file diff --git a/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx b/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx index 3007a59d..06273209 100644 --- a/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx +++ b/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx @@ -92,4 +92,4 @@ const CheckboxGroup: React.FC = ({ ); }; -export default CheckboxGroup; +export default CheckboxGroup; \ No newline at end of file diff --git a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx index c87498b0..ff6f4561 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx @@ -379,7 +379,7 @@ const TrainingAreaItem: React.FC<

    - {fetchStatusInfo} + {fetchStatusInfo()}

    diff --git a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx index 0b252136..a74983bd 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx @@ -1,7 +1,7 @@ import { MapComponent, MapCursorToolTip } from "@/components/map"; import { GeoJSONType, PaginatedTrainingArea } from "@/types"; import { GeoJSONSource, Map } from "maplibre-gl"; -import { RefObject, useCallback, useEffect, useState } from "react"; +import { RefObject, useCallback, useEffect, useMemo, useState } from "react"; import { useCreateTrainingArea, useGetTrainingDatasetLabels, @@ -273,7 +273,7 @@ const TrainingAreaMap = ({ return "bg-black"; }; - const getFeedbackMessage = () => { + const getFeedbackMessage = useMemo(() => { if (featureArea !== 0) { if (featureArea < MIN_TRAINING_AREA_SIZE) return "Area too small. Expand to meet minimum size requirement."; @@ -290,7 +290,7 @@ const TrainingAreaMap = ({ return "Zoom in up to zoom 18 to see the fetched labels."; } return; - }; + }, [featureArea, MIN_TRAINING_AREA_SIZE, MAX_TRAINING_AREA_SIZE, showLabelsToolTip]) return ( )} -

    {getFeedbackMessage()}

    +

    {getFeedbackMessage}

    ); diff --git a/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts b/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts index 4701e5e2..ed8c95c7 100644 --- a/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts +++ b/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts @@ -1,10 +1,9 @@ import { useQuery } from "@tanstack/react-query"; import { getTMSTileJSONQueryOptions } from "@/features/model-creation/api/factory"; + export const useGetTMSTileJSON = (url: string) => { return useQuery({ ...getTMSTileJSONQueryOptions(url), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, }); }; diff --git a/frontend/src/features/model-creation/hooks/use-training-areas.ts b/frontend/src/features/model-creation/hooks/use-training-areas.ts index a9182eef..3171317d 100644 --- a/frontend/src/features/model-creation/hooks/use-training-areas.ts +++ b/frontend/src/features/model-creation/hooks/use-training-areas.ts @@ -21,8 +21,6 @@ import axios from "axios"; export const useGetTrainingAreas = (datasetId: number, offset: number) => { return useQuery({ ...getTrainingAreasQueryOptions(datasetId, offset), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, }); }; @@ -137,8 +135,6 @@ export const useGetTrainingDatasetLabels = ( ) => { return useQuery({ ...getTrainingDatasetLabelsQueryOptions(datasetId, bbox), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, // Don't fetch when the bbox is empty enabled: bbox !== "" && currentZoom >= MIN_ZOOM_LEVEL_FOR_TRAINING_AREA_LABELS, @@ -148,8 +144,6 @@ export const useGetTrainingDatasetLabels = ( export const useGetTrainingAreaLabels = (aoiId: number, enabled: boolean) => { return useQuery({ ...getTrainingAreaLabelsQueryOptions(aoiId), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, enabled: enabled, }); }; @@ -161,8 +155,6 @@ export const useGetTrainingArea = ( ) => { return useQuery({ ...getTrainingAreaQueryOptions(aoiId), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, enabled: enabled, refetchInterval: refetchInterval, }); diff --git a/frontend/src/features/model-creation/hooks/use-training-datasets.ts b/frontend/src/features/model-creation/hooks/use-training-datasets.ts index 7ee395a4..366a35c3 100644 --- a/frontend/src/features/model-creation/hooks/use-training-datasets.ts +++ b/frontend/src/features/model-creation/hooks/use-training-datasets.ts @@ -9,8 +9,6 @@ import { export const useGetTrainingDatasets = (searchQuery: string) => { return useQuery({ ...getTrainingDatasetsQueryOptions(searchQuery), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, }); }; diff --git a/frontend/src/features/models/components/directory-tree.tsx b/frontend/src/features/models/components/directory-tree.tsx index 730a7462..0766c338 100644 --- a/frontend/src/features/models/components/directory-tree.tsx +++ b/frontend/src/features/models/components/directory-tree.tsx @@ -16,6 +16,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { getTrainingWorkspaceQueryOptions } from "@/features/models/api/factory"; import { API_ENDPOINTS, apiClient } from "@/services"; import { Spinner } from "@/components/ui/spinner"; +import { TCSSWithVars } from "@/types"; type DirectoryTreeProps = { datasetId: number; @@ -135,24 +136,24 @@ const DirectoryTree: React.FC = ({ const subdirectories = dir && currentDepth < maxDepth ? await Promise.all( - Object.keys(dir).map(async (key: string) => { - const fullPath = currentDirectory - ? `${currentDirectory}/${key}/` - : key; - const subDirData = await fetchDirectoryRecursive( - fullPath, - currentDepth + 1, - maxDepth, - ); - return { - [key]: { - ...subDirData, - size: dir[key]?.size || 0, - length: dir[key]?.len || 0, - }, - }; - }), - ) + Object.keys(dir).map(async (key: string) => { + const fullPath = currentDirectory + ? `${currentDirectory}/${key}/` + : key; + const subDirData = await fetchDirectoryRecursive( + fullPath, + currentDepth + 1, + maxDepth, + ); + return { + [key]: { + ...subDirData, + size: dir[key]?.size || 0, + length: dir[key]?.len || 0, + }, + }; + }), + ) : []; return { @@ -248,8 +249,7 @@ const DirectoryTree: React.FC = ({ ); return ( - //@ts-expect-error bad type definition - + diff --git a/frontend/src/features/models/hooks/use-dataset.ts b/frontend/src/features/models/hooks/use-dataset.ts index d8e725e3..659bdba3 100644 --- a/frontend/src/features/models/hooks/use-dataset.ts +++ b/frontend/src/features/models/hooks/use-dataset.ts @@ -4,8 +4,6 @@ import { getTrainingDatasetQueryOptions } from "../api/factory"; export const useGetTrainingDataset = (id: number, enabled: boolean = !!id) => { return useQuery({ ...getTrainingDatasetQueryOptions(id), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, enabled: enabled, }); }; diff --git a/frontend/src/features/models/hooks/use-models.ts b/frontend/src/features/models/hooks/use-models.ts index aae63e2d..7475cdad 100644 --- a/frontend/src/features/models/hooks/use-models.ts +++ b/frontend/src/features/models/hooks/use-models.ts @@ -47,8 +47,6 @@ export const useModels = ({ id, userId, }), - //@ts-expect-error bad type definition - throwOnError: (error) => error.response?.status >= 500, }); }; @@ -59,8 +57,6 @@ export const useModelDetails = ( ) => { return useQuery({ ...getModelDetailsQueryOptions(id, refetchInterval), - //@ts-expect-error bad type definition - throwOnError: (error) => error.response?.status >= 500, retry: (_, error) => { // When a model is not found, don't retry. //@ts-expect-error bad type definition @@ -73,8 +69,6 @@ export const useModelDetails = ( export const useModelsMapData = () => { return useQuery({ ...getModelsMapDataQueryOptions(), - //@ts-expect-error bad type definition - throwOnError: (error) => error.response?.status >= 500, }); }; diff --git a/frontend/src/features/models/hooks/use-training.ts b/frontend/src/features/models/hooks/use-training.ts index 91f50732..395e8573 100644 --- a/frontend/src/features/models/hooks/use-training.ts +++ b/frontend/src/features/models/hooks/use-training.ts @@ -14,8 +14,6 @@ export const useTrainingDetails = ( return useQuery({ ...getTrainingDetailsQueryOptions(id), //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, - //@ts-expect-error bad type definition refetchInterval: refetchInterval, enabled: id !== null, }); @@ -24,8 +22,6 @@ export const useTrainingDetails = ( export const useTrainingStatus = (taskId: string) => { return useQuery({ ...getTrainingStatusQueryOptions(taskId), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, refetchInterval: 10000, // 10 seconds }); }; @@ -33,8 +29,6 @@ export const useTrainingStatus = (taskId: string) => { export const useTrainingFeedbacks = (id: number) => { return useQuery({ ...getTrainingFeedbacksQueryOptions(id), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, enabled: id !== null, }); }; @@ -44,8 +38,6 @@ export const useTrainingWorkspace = ( ) => { return useQuery({ ...getTrainingWorkspaceQueryOptions(trainingId, directory_name), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, enabled: trainingId !== null, }); }; @@ -58,8 +50,6 @@ export const useTrainingHistory = ( ) => { return useQuery({ ...getTrainingHistoryQueryOptions(modelId, offset, limit, ordering), - //@ts-expect-error bad type definition - throwOnError: (error) => error?.response?.status >= 500, refetchInterval: 10000, // 10 seconds }); }; diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index ff772742..70e2b58a 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -4,7 +4,7 @@ import { GeoJSON } from "geojson"; import { Feature } from "./api"; /* eslint-disable @typescript-eslint/no-empty-object-type */ -export interface IconProps extends React.SVGProps {} +export interface IconProps extends React.SVGProps { } export type ShoelaceSlotProps = { slot?: string; @@ -67,3 +67,10 @@ export type TNavBarLinks = { title: string; href: string; }[]; + + +// Extending with shoelace properties. +export type TCSSWithVars = React.CSSProperties & { + '--size'?: string; + '--indent-guide-width'?: string +} \ No newline at end of file diff --git a/frontend/src/utils/error-handler.ts b/frontend/src/utils/error-handler.ts new file mode 100644 index 00000000..91a10fe2 --- /dev/null +++ b/frontend/src/utils/error-handler.ts @@ -0,0 +1,24 @@ +// errorHandlers.ts +import axios from 'axios'; +import { NavigateFunction } from 'react-router-dom'; +import { APPLICATION_ROUTES } from '@/utils'; + +export const handleErrorNavigation = (error: unknown, navigate: NavigateFunction) => { + const currentPath = window.location.pathname; + if (axios.isAxiosError(error)) { + navigate(APPLICATION_ROUTES.NOTFOUND, { + state: { + from: currentPath, + error: error.response?.data?.detail, + }, + }); + } else { + const err = error as Error; + navigate(APPLICATION_ROUTES.NOTFOUND, { + state: { + from: currentPath, + error: err.message, + }, + }); + } +} diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 814d4e87..19e2a1e1 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -7,3 +7,4 @@ export * from "./string-utils"; export * from "./regex-utils"; export * from "./geometry-utils"; export * from "./general-utils"; +export * from './error-handler'; From 5c8fc73ad23bcca9917864d4bf62d2f66aaadb5d Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Fri, 27 Dec 2024 22:57:00 +0100 Subject: [PATCH 18/33] chore: fixed deployment bug --- .../src/app/providers/models-provider.tsx | 25 +-- frontend/src/app/routes/account/models.tsx | 1 - .../src/app/routes/models/model-details.tsx | 2 +- .../src/app/routes/models/models-list.tsx | 1 - frontend/src/app/routes/start-mapping.tsx | 78 +++++---- .../components/map/controls/layer-control.tsx | 14 +- frontend/src/components/map/map.tsx | 14 +- frontend/src/components/shared/faqs/faqs.tsx | 78 +++++---- .../shared/static-page-header/header.tsx | 1 - frontend/src/components/ui/banner/banner.tsx | 24 +-- .../src/components/ui/dropdown/dropdown.tsx | 4 +- .../ui/form/checkbox-group/checkbox-group.tsx | 2 +- frontend/src/components/ui/image/image.tsx | 1 - frontend/src/enums/common.ts | 6 +- .../components/progress-bar.tsx | 13 +- .../components/progress-buttons.tsx | 12 +- .../training-area/training-area-item.tsx | 9 +- .../training-area/training-area-list.tsx | 1 - .../training-area/training-area-map.tsx | 34 ++-- .../training-dataset/create-new.tsx | 8 +- .../model-creation/hooks/use-tms-tilejson.ts | 1 - .../models/components/directory-tree.tsx | 36 ++--- .../components/filters/status-filter.tsx | 2 +- .../components/maps/training-area-map.tsx | 16 +- .../models/components/model-details-popup.tsx | 148 +++++++++--------- .../src/features/models/hooks/use-models.ts | 2 +- .../start-mapping/components/header.tsx | 4 +- .../components/logo-with-dropdown.tsx | 12 +- .../start-mapping/components/map/map.tsx | 22 +-- .../components/model-settings.tsx | 116 +++++++------- frontend/src/hooks/use-dropdown-menu.ts | 2 +- frontend/src/hooks/use-screen-size.ts | 4 +- frontend/src/styles/index.css | 2 +- frontend/src/types/common.ts | 9 +- frontend/src/utils/error-handler.ts | 47 +++--- frontend/src/utils/index.ts | 2 +- 36 files changed, 371 insertions(+), 382 deletions(-) diff --git a/frontend/src/app/providers/models-provider.tsx b/frontend/src/app/providers/models-provider.tsx index 2b0a9881..8c949a94 100644 --- a/frontend/src/app/providers/models-provider.tsx +++ b/frontend/src/app/providers/models-provider.tsx @@ -223,8 +223,8 @@ const ModelsContext = createContext<{ validateEditMode: boolean; }>({ formData: initialFormState, - setFormData: () => { }, - handleChange: () => { }, + setFormData: () => {}, + handleChange: () => {}, createNewTrainingDatasetMutation: {} as UseMutationResult< TTrainingDataset, Error, @@ -239,13 +239,13 @@ const ModelsContext = createContext<{ >, hasLabeledTrainingAreas: false, hasAOIsWithGeometry: false, - resetState: () => { }, + resetState: () => {}, isEditMode: false, modelId: "", getFullPath: () => "", - handleModelCreationAndUpdate: () => { }, + handleModelCreationAndUpdate: () => {}, trainingDatasetCreationInProgress: false, - handleTrainingDatasetCreation: () => { }, + handleTrainingDatasetCreation: () => {}, validateEditMode: false, }); @@ -293,7 +293,8 @@ export const ModelsProvider: React.FC<{ ); // Will be used in the route validator component to delay the redirection for a while until the data are retrieved - const validateEditMode = formData.selectedTrainingDatasetId !== "" && formData.tmsURL !== "" + const validateEditMode = + formData.selectedTrainingDatasetId !== "" && formData.tmsURL !== ""; // Fetch and prefill model details useEffect(() => { @@ -418,17 +419,17 @@ export const ModelsProvider: React.FC<{ }); // Confirm that all the training areas labels has been retrieved - const hasLabeledTrainingAreas = formData.trainingAreas.length > 0 && + const hasLabeledTrainingAreas = + formData.trainingAreas.length > 0 && formData.trainingAreas.filter( (aoi: Feature) => aoi.properties.label_fetched === null, - ).length === 0 + ).length === 0; // Confirm that all of the training areas has a geometry - const hasAOIsWithGeometry = formData.trainingAreas.length > 0 && + const hasAOIsWithGeometry = + formData.trainingAreas.length > 0 && formData.trainingAreas.filter((aoi: Feature) => aoi.geometry === null) - .length === 0 - - + .length === 0; const resetState = () => { setFormData(initialFormState); diff --git a/frontend/src/app/routes/account/models.tsx b/frontend/src/app/routes/account/models.tsx index ad6af44f..be05bef6 100644 --- a/frontend/src/app/routes/account/models.tsx +++ b/frontend/src/app/routes/account/models.tsx @@ -39,7 +39,6 @@ export const UserModelsPage = () => { updateQuery, } = useModelsListFilters(undefined, user?.osm_id); - const renderContent = () => { if (data?.count === 0) { return ; diff --git a/frontend/src/app/routes/models/model-details.tsx b/frontend/src/app/routes/models/model-details.tsx index 18a9eb9b..b0d9b7bd 100644 --- a/frontend/src/app/routes/models/model-details.tsx +++ b/frontend/src/app/routes/models/model-details.tsx @@ -11,7 +11,7 @@ import { ModelFilesDialog } from "@/features/models/components/dialogs"; import { ModelDetailsSkeleton } from "@/features/models/components/skeletons"; import { useModelDetails } from "@/features/models/hooks/use-models"; import { useDialog } from "@/hooks/use-dialog"; -import { APP_CONTENT, } from "@/utils"; +import { APP_CONTENT } from "@/utils"; import { useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Image } from "@/components/ui/image"; diff --git a/frontend/src/app/routes/models/models-list.tsx b/frontend/src/app/routes/models/models-list.tsx index 7cfb0f47..da21fcf4 100644 --- a/frontend/src/app/routes/models/models-list.tsx +++ b/frontend/src/app/routes/models/models-list.tsx @@ -69,7 +69,6 @@ export const ModelsPage = () => { isError: modelsMapDataIsError, } = useModelsMapData(); - // Mapview toggling interaction useEffect(() => { if (mapViewIsActive) { diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index efaab9c9..524db0c1 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -114,11 +114,10 @@ export const StartMappingPage = () => { rejected: [], }); - const modelPredictionsExist = modelPredictions.accepted.length > 0 || + const modelPredictionsExist = + modelPredictions.accepted.length > 0 || modelPredictions.rejected.length > 0 || - modelPredictions.all.length > 0 - - + modelPredictions.all.length > 0; const updateQuery = useCallback( (newParams: TQueryParams) => { @@ -166,45 +165,44 @@ export const StartMappingPage = () => { const mapLayers = [ ...(modelPredictions.accepted.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .acceptedPredictions, - subLayers: [ - ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .acceptedPredictions, + subLayers: [ + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ...(modelPredictions.rejected.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .rejectedPredictions, - subLayers: [ - REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .rejectedPredictions, + subLayers: [ + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), ...(modelPredictions.all.length > 0 ? [ - { - value: - startMappingPageContent.map.controls.legendControl - .predictionResults, - subLayers: [ - ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, - ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ], - }, - ] + { + value: + startMappingPageContent.map.controls.legendControl + .predictionResults, + subLayers: [ + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ], + }, + ] : []), - ] - + ]; const handleAllFeaturesDownload = useCallback(async () => { geoJSONDowloader( @@ -260,8 +258,7 @@ export const StartMappingPage = () => { }, { name: startMappingPageContent.buttons.download.options.acceptedFeatures, - value: - startMappingPageContent.buttons.download.options.acceptedFeatures, + value: startMappingPageContent.buttons.download.options.acceptedFeatures, onClick: handleAcceptedFeaturesDownload, showOnMobile: true, }, @@ -269,8 +266,7 @@ export const StartMappingPage = () => { name: startMappingPageContent.buttons.download.options .openAllFeaturesInJOSM, value: - startMappingPageContent.buttons.download.options - .openAllFeaturesInJOSM, + startMappingPageContent.buttons.download.options.openAllFeaturesInJOSM, onClick: handleAllFeaturesDownloadToJOSM, showOnMobile: false, }, @@ -283,7 +279,7 @@ export const StartMappingPage = () => { onClick: handleAcceptedFeaturesDownloadToJOSM, showOnMobile: false, }, - ] + ]; const handleModelDetailsPopup = useCallback(() => { setShowModelDetailsPopup((prev) => !prev); diff --git a/frontend/src/components/map/controls/layer-control.tsx b/frontend/src/components/map/controls/layer-control.tsx index 81e902af..cf6bc1f2 100644 --- a/frontend/src/components/map/controls/layer-control.tsx +++ b/frontend/src/components/map/controls/layer-control.tsx @@ -40,12 +40,12 @@ export const LayerControl = ({ ]; const baseLayers: TBasemaps = basemaps ? [ - { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, - { - value: BASEMAPS.GOOGLE_SATELLITE, - subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, - }, - ] + { value: BASEMAPS.OSM, subLayer: OSM_BASEMAP_LAYER_ID }, + { + value: BASEMAPS.GOOGLE_SATELLITE, + subLayer: GOOGLE_SATELLITE_BASEMAP_LAYER_ID, + }, + ] : []; return { layers_, baseLayers }; }, [layers, openAerialMap, basemaps]); @@ -176,4 +176,4 @@ export const LayerControl = ({ ); -}; \ No newline at end of file +}; diff --git a/frontend/src/components/map/map.tsx b/frontend/src/components/map/map.tsx index 3b13602a..a146ac72 100644 --- a/frontend/src/components/map/map.tsx +++ b/frontend/src/components/map/map.tsx @@ -69,13 +69,14 @@ export const MapComponent: React.FC = ({ }) => { return (
    - {map ? + {map ? ( <>
    {currentZoom && zoomControls ? ( @@ -110,8 +111,7 @@ export const MapComponent: React.FC = ({ )}
    - : null - } + ) : null} {/* Order according to how they'll be rendered */} {basemaps && } {openAerialMap && oamTileJSONURL && ( diff --git a/frontend/src/components/shared/faqs/faqs.tsx b/frontend/src/components/shared/faqs/faqs.tsx index 176fd784..bf3df6f9 100644 --- a/frontend/src/components/shared/faqs/faqs.tsx +++ b/frontend/src/components/shared/faqs/faqs.tsx @@ -6,44 +6,42 @@ import { APPLICATION_ROUTES } from "@/utils"; import { TFAQs } from "@/types"; import { ChevronDownIcon } from "@/components/ui/icons"; -export const FAQs = - ({ - faqs = APP_CONTENT.homepage.faqs.content, - disableSeeMoreButton, - }: { - faqs?: TFAQs; - disableSeeMoreButton?: boolean; - }) => { - return ( -
    -

    - {APP_CONTENT.homepage.faqs.sectionTitle} -

    -
    -
    - {faqs.map((faq, id) => ( - - ))} -
    - {!disableSeeMoreButton && ( - -

    - {APP_CONTENT.homepage.faqs.cta} - -

    - - )} +export const FAQs = ({ + faqs = APP_CONTENT.homepage.faqs.content, + disableSeeMoreButton, +}: { + faqs?: TFAQs; + disableSeeMoreButton?: boolean; +}) => { + return ( +
    +

    + {APP_CONTENT.homepage.faqs.sectionTitle} +

    +
    +
    + {faqs.map((faq, id) => ( + + ))}
    -
    - ); - } - + {!disableSeeMoreButton && ( + +

    + {APP_CONTENT.homepage.faqs.cta} + +

    + + )} +
    +
    + ); +}; diff --git a/frontend/src/components/shared/static-page-header/header.tsx b/frontend/src/components/shared/static-page-header/header.tsx index 4e9ef817..b88382a1 100644 --- a/frontend/src/components/shared/static-page-header/header.tsx +++ b/frontend/src/components/shared/static-page-header/header.tsx @@ -1,4 +1,3 @@ - import styles from "./header.module.css"; export const Header = ({ title }: { title: string }) => { diff --git a/frontend/src/components/ui/banner/banner.tsx b/frontend/src/components/ui/banner/banner.tsx index 23bdcb02..6807df60 100644 --- a/frontend/src/components/ui/banner/banner.tsx +++ b/frontend/src/components/ui/banner/banner.tsx @@ -31,17 +31,19 @@ const Banner = () => { return null; } - return
    - - {data?.[0]?.message} - - -
    + return ( +
    + + {data?.[0]?.message} + + +
    + ); }; export default Banner; diff --git a/frontend/src/components/ui/dropdown/dropdown.tsx b/frontend/src/components/ui/dropdown/dropdown.tsx index a228b03e..e53e38f7 100644 --- a/frontend/src/components/ui/dropdown/dropdown.tsx +++ b/frontend/src/components/ui/dropdown/dropdown.tsx @@ -18,7 +18,7 @@ export type DropdownMenuItem = { }; type DropDownProps = { - placement?: DropdownPlacement + placement?: DropdownPlacement; children?: React.ReactNode; onDropdownShow?: (event: React.MouseEvent) => void; onDropdownHide?: (event: React.MouseEvent) => void; @@ -160,4 +160,4 @@ const DropDown: React.FC = ({ ); }; -export default DropDown; \ No newline at end of file +export default DropDown; diff --git a/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx b/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx index 06273209..3007a59d 100644 --- a/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx +++ b/frontend/src/components/ui/form/checkbox-group/checkbox-group.tsx @@ -92,4 +92,4 @@ const CheckboxGroup: React.FC = ({ ); }; -export default CheckboxGroup; \ No newline at end of file +export default CheckboxGroup; diff --git a/frontend/src/components/ui/image/image.tsx b/frontend/src/components/ui/image/image.tsx index ede55849..0bfc4bcb 100644 --- a/frontend/src/components/ui/image/image.tsx +++ b/frontend/src/components/ui/image/image.tsx @@ -20,7 +20,6 @@ const Image: React.FC = ({ className, placeHolder, }) => { - const [isLoading, setIsLoading] = useState(true); const [imageSrc, setImageSrc] = useState(src); diff --git a/frontend/src/enums/common.ts b/frontend/src/enums/common.ts index 84ae6008..95b35c75 100644 --- a/frontend/src/enums/common.ts +++ b/frontend/src/enums/common.ts @@ -56,6 +56,6 @@ export enum DrawerPlacements { export enum DropdownPlacement { BOTTOM_START = "bottom-start", - BOTTOM_END = 'bottom-end', - TOP_END = 'top-end' -} \ No newline at end of file + BOTTOM_END = "bottom-end", + TOP_END = "top-end", +} diff --git a/frontend/src/features/model-creation/components/progress-bar.tsx b/frontend/src/features/model-creation/components/progress-bar.tsx index 7bf7aa37..a2ff26e8 100644 --- a/frontend/src/features/model-creation/components/progress-bar.tsx +++ b/frontend/src/features/model-creation/components/progress-bar.tsx @@ -10,7 +10,11 @@ type ProgressBarProps = { pages: { id: number; title: string; icon: React.ElementType; path: string }[]; }; -const ProgressBar: React.FC = ({ currentPath, currentPageIndex, pages }) => { +const ProgressBar: React.FC = ({ + currentPath, + currentPageIndex, + pages, +}) => { const navigate = useNavigate(); const { getFullPath, isEditMode } = useModelsContext(); const activeStepRef = useRef(null); @@ -57,9 +61,10 @@ const ProgressBar: React.FC = ({ currentPath, currentPageIndex ) : ( diff --git a/frontend/src/features/model-creation/components/progress-buttons.tsx b/frontend/src/features/model-creation/components/progress-buttons.tsx index 7a6e438d..4a9feb7c 100644 --- a/frontend/src/features/model-creation/components/progress-buttons.tsx +++ b/frontend/src/features/model-creation/components/progress-buttons.tsx @@ -95,11 +95,11 @@ const ProgressButtons: React.FC = ({ if (currentPath.includes(MODELS_ROUTES.DETAILS)) { return ( formData.modelName.length >= - FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_NAME] - .minLength && + FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_NAME] + .minLength && formData.modelDescription.length >= - FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_DESCRIPTION] - .minLength + FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.MODEL_DESCRIPTION] + .minLength ); } else if (currentPath.includes(MODELS_ROUTES.TRAINING_DATASET)) { // if the user hasn't selected any of the options, then they can not proceed to next page. @@ -118,8 +118,8 @@ const ProgressButtons: React.FC = ({ return ( formData.tmsURLValidation.valid && formData.datasetName.length >= - FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.DATASET_NAME] - .minLength + FORM_VALIDATION_CONFIG[MODEL_CREATION_FORM_NAME.DATASET_NAME] + .minLength ); } else if ( formData.trainingDatasetOption === TrainingDatasetOption.USE_EXISTING diff --git a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx index ff6f4561..a8517810 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-item.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-item.tsx @@ -136,7 +136,7 @@ const TrainingAreaItem: React.FC< if ( getTrainingArea.isError || getTrainingArea.data?.properties.label_status === - LabelStatus.NOT_DOWNLOADED + LabelStatus.NOT_DOWNLOADED ) { handleLabelError(); } else if ( @@ -335,12 +335,11 @@ const TrainingAreaItem: React.FC< trainingAreaId: trainingArea.id, }), }, - ] - + ]; const trainingAreaSize = trainingArea.geometry ? formatAreaInAppropriateUnit(calculateGeoJSONArea(trainingArea)) - : "0 m²" + : "0 m²"; const fetchStatusInfo = () => { if (labelState.isFetching || trainingAreaLabelsMutation.isPending) { @@ -355,7 +354,7 @@ const TrainingAreaItem: React.FC< : "Fetched recently"; } return "No labels yet"; - } + }; return ( <> diff --git a/frontend/src/features/model-creation/components/training-area/training-area-list.tsx b/frontend/src/features/model-creation/components/training-area/training-area-list.tsx index fa55244b..9e021ec2 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-list.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-list.tsx @@ -39,7 +39,6 @@ const TrainingAreaList = ({ refetchInterval: OSM_LAST_UPDATED_POOLING_INTERVAL_MS, }); - return (
    diff --git a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx index a74983bd..cc0b495c 100644 --- a/frontend/src/features/model-creation/components/training-area/training-area-map.tsx +++ b/frontend/src/features/model-creation/components/training-area/training-area-map.tsx @@ -32,7 +32,6 @@ import { useToolTipVisibility } from "@/hooks/use-tooltip-visibility"; import { useMapLayers } from "@/hooks/use-map-layer"; import { TerraDraw } from "terra-draw"; - // Debounce delay in milliseconds. const DEBOUNCE_DELAY: number = 300; @@ -290,7 +289,12 @@ const TrainingAreaMap = ({ return "Zoom in up to zoom 18 to see the fetched labels."; } return; - }, [featureArea, MIN_TRAINING_AREA_SIZE, MAX_TRAINING_AREA_SIZE, showLabelsToolTip]) + }, [ + featureArea, + MIN_TRAINING_AREA_SIZE, + MAX_TRAINING_AREA_SIZE, + showLabelsToolTip, + ]); return ( 0 ? [ - { - value: "Training Labels", - subLayers: [ - trainingDatasetLabelsLayerId, - trainingDatasetLabelsOutlineLayerId, - ], - }, - ] + { + value: "Training Labels", + subLayers: [ + trainingDatasetLabelsLayerId, + trainingDatasetLabelsOutlineLayerId, + ], + }, + ] : []), ]} > diff --git a/frontend/src/features/model-creation/components/training-dataset/create-new.tsx b/frontend/src/features/model-creation/components/training-dataset/create-new.tsx index b8811efc..37a36e59 100644 --- a/frontend/src/features/model-creation/components/training-dataset/create-new.tsx +++ b/frontend/src/features/model-creation/components/training-dataset/create-new.tsx @@ -11,10 +11,10 @@ import { useEffect } from "react"; const CreateNewTrainingDatasetForm = () => { const { formData, handleChange } = useModelsContext(); - const tmsURLHelpText = formData.tmsURLValidation.message.length > 0 - ? formData.tmsURLValidation.message - : MODEL_CREATION_CONTENT.trainingDataset.form.tmsURL.helpText; - + const tmsURLHelpText = + formData.tmsURLValidation.message.length > 0 + ? formData.tmsURLValidation.message + : MODEL_CREATION_CONTENT.trainingDataset.form.tmsURL.helpText; useEffect(() => { // Shoelace will handle the validation when it's more than 0 characters. diff --git a/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts b/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts index ed8c95c7..d902321f 100644 --- a/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts +++ b/frontend/src/features/model-creation/hooks/use-tms-tilejson.ts @@ -1,7 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { getTMSTileJSONQueryOptions } from "@/features/model-creation/api/factory"; - export const useGetTMSTileJSON = (url: string) => { return useQuery({ ...getTMSTileJSONQueryOptions(url), diff --git a/frontend/src/features/models/components/directory-tree.tsx b/frontend/src/features/models/components/directory-tree.tsx index 0766c338..79c532a8 100644 --- a/frontend/src/features/models/components/directory-tree.tsx +++ b/frontend/src/features/models/components/directory-tree.tsx @@ -136,24 +136,24 @@ const DirectoryTree: React.FC = ({ const subdirectories = dir && currentDepth < maxDepth ? await Promise.all( - Object.keys(dir).map(async (key: string) => { - const fullPath = currentDirectory - ? `${currentDirectory}/${key}/` - : key; - const subDirData = await fetchDirectoryRecursive( - fullPath, - currentDepth + 1, - maxDepth, - ); - return { - [key]: { - ...subDirData, - size: dir[key]?.size || 0, - length: dir[key]?.len || 0, - }, - }; - }), - ) + Object.keys(dir).map(async (key: string) => { + const fullPath = currentDirectory + ? `${currentDirectory}/${key}/` + : key; + const subDirData = await fetchDirectoryRecursive( + fullPath, + currentDepth + 1, + maxDepth, + ); + return { + [key]: { + ...subDirData, + size: dir[key]?.size || 0, + length: dir[key]?.len || 0, + }, + }; + }), + ) : []; return { diff --git a/frontend/src/features/models/components/filters/status-filter.tsx b/frontend/src/features/models/components/filters/status-filter.tsx index 4ad32dfe..0d155575 100644 --- a/frontend/src/features/models/components/filters/status-filter.tsx +++ b/frontend/src/features/models/components/filters/status-filter.tsx @@ -58,7 +58,7 @@ const StatusFilter: React.FC = ({ ]; const categoryLabel = statusCategories.filter( (status) => status.apiValue === query[SEARCH_PARAMS.status], - ) + ); const { dropdownIsOpened, onDropdownHide, onDropdownShow } = useDropdownMenu(); diff --git a/frontend/src/features/models/components/maps/training-area-map.tsx b/frontend/src/features/models/components/maps/training-area-map.tsx index b508f1d9..0b60b082 100644 --- a/frontend/src/features/models/components/maps/training-area-map.tsx +++ b/frontend/src/features/models/components/maps/training-area-map.tsx @@ -81,7 +81,7 @@ export const TrainingAreaMap = ({ const tileJSONURL = extractTileJSONURL(tmsURL); - const trainingAreasSourceId = `training-areas-for-${trainingAreaId}` + const trainingAreasSourceId = `training-areas-for-${trainingAreaId}`; const mapLayers: LayerSpecification[] = vectorLayers.flatMap((layer) => { const { fill, outline } = getLayerConfigs(layer.id); @@ -105,7 +105,6 @@ export const TrainingAreaMap = ({ ]; }); - const sources = [ { id: trainingAreasSourceId, @@ -114,13 +113,12 @@ export const TrainingAreaMap = ({ url: `pmtiles://${file}`, } as SourceSpecification, }, - ] + ]; const layerControlLayers = vectorLayers.map((layer) => ({ value: `Training ${layer.id}`, subLayers: [`${layer.id}_fill`, `${layer.id}_outline`], - })) - + })); const fitToBounds = useCallback(() => { if ( @@ -172,15 +170,15 @@ export const TrainingAreaMap = ({ ${Object.entries(feature.properties) - .map( - ([key, value]) => ` + .map( + ([key, value]) => ` `, - ) - .join("")} + ) + .join("")}
    ${key} ${typeof value === "boolean" ? JSON.stringify(value) : value}
    diff --git a/frontend/src/features/models/components/model-details-popup.tsx b/frontend/src/features/models/components/model-details-popup.tsx index 50bb585e..b788116d 100644 --- a/frontend/src/features/models/components/model-details-popup.tsx +++ b/frontend/src/features/models/components/model-details-popup.tsx @@ -43,83 +43,83 @@ const ModelDetailsPopUp = ({ ); const { isSmallViewport } = useScreenSize(); - const popupContent = -
    -

    - {startMappingPageContent.modelDetails.popover.modelId}:{" "} - {model?.id ?? data?.id} -

    -

    - {startMappingPageContent.modelDetails.popover.description}:{" "} - - {model?.description ?? data?.description} - -

    -

    - {startMappingPageContent.modelDetails.popover.lastModified}:{" "} - - {extractDatePart( - model?.last_modified ?? (data?.last_modified as string), - )} - -

    -

    - {startMappingPageContent.modelDetails.popover.trainingId}:{" "} - - {model?.published_training ?? data?.published_training} - -

    -

    - {startMappingPageContent.modelDetails.popover.datasetId}:{" "} - - {model?.dataset ?? data?.dataset} - -

    -

    - {startMappingPageContent.modelDetails.popover.datasetName}:{" "} - - - {trainingDatasetIsError - ? "N/A" - : truncateString(trainingDataset?.name, 40)}{" "} + const popupContent = ( + +

    +

    + {startMappingPageContent.modelDetails.popover.modelId}:{" "} + {model?.id ?? data?.id} +

    +

    + {startMappingPageContent.modelDetails.popover.description}:{" "} + + {model?.description ?? data?.description} - -

    - -

    - {startMappingPageContent.modelDetails.popover.zoomLevel}:{" "} - - - {trainingDetailsError - ? "N/A" - : trainingDetails?.zoom_level?.join(", ")}{" "} +

    +

    + {startMappingPageContent.modelDetails.popover.lastModified}:{" "} + + {extractDatePart( + model?.last_modified ?? (data?.last_modified as string), + )} - -

    +

    +

    + {startMappingPageContent.modelDetails.popover.trainingId}:{" "} + + {model?.published_training ?? data?.published_training} + +

    +

    + {startMappingPageContent.modelDetails.popover.datasetId}:{" "} + {model?.dataset ?? data?.dataset} +

    +

    + {startMappingPageContent.modelDetails.popover.datasetName}:{" "} + + + {trainingDatasetIsError + ? "N/A" + : truncateString(trainingDataset?.name, 40)}{" "} + + +

    -

    - {startMappingPageContent.modelDetails.popover.accuracy}:{" "} - - {roundNumber(model?.accuracy ?? (data?.accuracy as number), 2)}% - -

    -

    - {startMappingPageContent.modelDetails.popover.baseModel}:{" "} - - {model?.base_model ?? data?.base_model} - -

    -
    - +

    + {startMappingPageContent.modelDetails.popover.zoomLevel}:{" "} + + + {trainingDetailsError + ? "N/A" + : trainingDetails?.zoom_level?.join(", ")}{" "} + + +

    + +

    + {startMappingPageContent.modelDetails.popover.accuracy}:{" "} + + {roundNumber(model?.accuracy ?? (data?.accuracy as number), 2)}% + +

    +

    + {startMappingPageContent.modelDetails.popover.baseModel}:{" "} + + {model?.base_model ?? data?.base_model} + +

    +
    +
    + ); if (isSmallViewport) { return ( diff --git a/frontend/src/features/models/hooks/use-models.ts b/frontend/src/features/models/hooks/use-models.ts index 7475cdad..3bee1979 100644 --- a/frontend/src/features/models/hooks/use-models.ts +++ b/frontend/src/features/models/hooks/use-models.ts @@ -171,7 +171,7 @@ export const useModelsListFilters = ( setQuery(newQuery); }, []); - const mapViewIsActive = query[SEARCH_PARAMS.mapIsActive] + const mapViewIsActive = query[SEARCH_PARAMS.mapIsActive]; const clearAllFilters = useCallback(() => { const resetParams = new URLSearchParams(); diff --git a/frontend/src/features/start-mapping/components/header.tsx b/frontend/src/features/start-mapping/components/header.tsx index 2d2c16d9..a6f67c42 100644 --- a/frontend/src/features/start-mapping/components/header.tsx +++ b/frontend/src/features/start-mapping/components/header.tsx @@ -103,8 +103,8 @@ const StartMappingHeader = ({ content={ !modelPredictionsExist ? startMappingPageContent.actions.disabledModeTooltip( - "see actions", - ) + "see actions", + ) : null } > diff --git a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx index 0f48ad2f..a2014d3d 100644 --- a/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx +++ b/frontend/src/features/start-mapping/components/logo-with-dropdown.tsx @@ -10,19 +10,15 @@ type BrandLogoWithDropDownProps = { isOpened: boolean; onClose: () => void; onShow: () => void; -} - +}; export const BrandLogoWithDropDown = function BrandLogoWithDropDown({ isOpened, onClose, onShow, }: BrandLogoWithDropDownProps) { - const navItems = navLinks.map((link, id) => ( -
  • +
  • - )) + )); - const navigate = useNavigate() + const navigate = useNavigate(); return ( - {showPopup && } + {showPopup && ( + + )} void; - isMobile?: boolean; - }) => { - const { - onDropdownHide: onModelSettingsDropdownHide, - onDropdownShow: onModelSettingsDropdownShow, - dropdownIsOpened, - toggleDropDown, - } = useDropdownMenu(); +export const ModelSettings = ({ + query, + updateQuery, + isMobile = false, +}: { + query: TQueryParams; + updateQuery: (newParams: TQueryParams) => void; + isMobile?: boolean; +}) => { + const { + onDropdownHide: onModelSettingsDropdownHide, + onDropdownShow: onModelSettingsDropdownShow, + dropdownIsOpened, + toggleDropDown, + } = useDropdownMenu(); - const handleQueryUpdate = (key: string, val: number | boolean) => { - // Keep the dropdown opened when making changes - onModelSettingsDropdownShow(); - updateQuery({ - [key]: val, - }); - }; + const handleQueryUpdate = (key: string, val: number | boolean) => { + // Keep the dropdown opened when making changes + onModelSettingsDropdownShow(); + updateQuery({ + [key]: val, + }); + }; - const modelSettings =
    + const modelSettings = ( +
    - handleQueryUpdate( - SEARCH_PARAMS.area, - Number(event.target.value), - ) + handleQueryUpdate(SEARCH_PARAMS.area, Number(event.target.value)) } min={0} />
    + ); - if (!isMobile) { - return ( - - - - } - className="rounded-xl" - > - {modelSettings} - - ); - } - return modelSettings; + if (!isMobile) { + return ( + + + + } + className="rounded-xl" + > + {modelSettings} + + ); } - ; + return modelSettings; +}; diff --git a/frontend/src/hooks/use-dropdown-menu.ts b/frontend/src/hooks/use-dropdown-menu.ts index 9a5dc1aa..efd09880 100644 --- a/frontend/src/hooks/use-dropdown-menu.ts +++ b/frontend/src/hooks/use-dropdown-menu.ts @@ -30,6 +30,6 @@ export const useDropdownMenu = () => { } }, [isOpened]); - const dropdownIsOpened = isOpened + const dropdownIsOpened = isOpened; return { onDropdownHide, onDropdownShow, dropdownIsOpened, toggleDropDown }; }; diff --git a/frontend/src/hooks/use-screen-size.ts b/frontend/src/hooks/use-screen-size.ts index f41e27f5..147c4920 100644 --- a/frontend/src/hooks/use-screen-size.ts +++ b/frontend/src/hooks/use-screen-size.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; /** * Custom hook to detect whether the current device is mobile, tablet based on the window width. @@ -37,7 +37,7 @@ const useScreenSize = () => { }; }, []); - const isSmallViewport = screenSize.isMobile || screenSize.isTablet + const isSmallViewport = screenSize.isMobile || screenSize.isTablet; return { ...screenSize, isSmallViewport }; }; diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index c4a3bf36..cfc64a10 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -4,7 +4,7 @@ body { font-family: var(--sl-font-sans); - pointer-events:all !important; + pointer-events: all !important; } :root, diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts index 70e2b58a..3d867cf0 100644 --- a/frontend/src/types/common.ts +++ b/frontend/src/types/common.ts @@ -4,7 +4,7 @@ import { GeoJSON } from "geojson"; import { Feature } from "./api"; /* eslint-disable @typescript-eslint/no-empty-object-type */ -export interface IconProps extends React.SVGProps { } +export interface IconProps extends React.SVGProps {} export type ShoelaceSlotProps = { slot?: string; @@ -68,9 +68,8 @@ export type TNavBarLinks = { href: string; }[]; - // Extending with shoelace properties. export type TCSSWithVars = React.CSSProperties & { - '--size'?: string; - '--indent-guide-width'?: string -} \ No newline at end of file + "--size"?: string; + "--indent-guide-width"?: string; +}; diff --git a/frontend/src/utils/error-handler.ts b/frontend/src/utils/error-handler.ts index 91a10fe2..9e96f41d 100644 --- a/frontend/src/utils/error-handler.ts +++ b/frontend/src/utils/error-handler.ts @@ -1,24 +1,27 @@ // errorHandlers.ts -import axios from 'axios'; -import { NavigateFunction } from 'react-router-dom'; -import { APPLICATION_ROUTES } from '@/utils'; +import axios from "axios"; +import { NavigateFunction } from "react-router-dom"; +import { APPLICATION_ROUTES } from "@/utils"; -export const handleErrorNavigation = (error: unknown, navigate: NavigateFunction) => { - const currentPath = window.location.pathname; - if (axios.isAxiosError(error)) { - navigate(APPLICATION_ROUTES.NOTFOUND, { - state: { - from: currentPath, - error: error.response?.data?.detail, - }, - }); - } else { - const err = error as Error; - navigate(APPLICATION_ROUTES.NOTFOUND, { - state: { - from: currentPath, - error: err.message, - }, - }); - } -} +export const handleErrorNavigation = ( + error: unknown, + navigate: NavigateFunction, +) => { + const currentPath = window.location.pathname; + if (axios.isAxiosError(error)) { + navigate(APPLICATION_ROUTES.NOTFOUND, { + state: { + from: currentPath, + error: error.response?.data?.detail, + }, + }); + } else { + const err = error as Error; + navigate(APPLICATION_ROUTES.NOTFOUND, { + state: { + from: currentPath, + error: err.message, + }, + }); + } +}; diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 19e2a1e1..bc188aff 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -7,4 +7,4 @@ export * from "./string-utils"; export * from "./regex-utils"; export * from "./geometry-utils"; export * from "./general-utils"; -export * from './error-handler'; +export * from "./error-handler"; From c1042a3e0732ca8632d5a5982b438413918601c3 Mon Sep 17 00:00:00 2001 From: jeafreezy Date: Sat, 28 Dec 2024 20:22:45 +0100 Subject: [PATCH 19/33] chore: housekeeping + added footer links --- frontend/src/app/index.tsx | 7 - frontend/src/app/providers/auth-provider.tsx | 7 +- .../src/app/providers/models-provider.tsx | 4 +- frontend/src/app/router.tsx | 2 +- frontend/src/app/routes/about.tsx | 18 +- frontend/src/app/routes/account/models.tsx | 12 +- frontend/src/app/routes/learn.tsx | 30 +- .../src/app/routes/models/confirmation.tsx | 14 +- .../src/app/routes/models/model-details.tsx | 11 +- .../src/app/routes/models/models-list.tsx | 5 +- frontend/src/app/routes/not-found.tsx | 19 +- frontend/src/app/routes/protected-route.tsx | 11 +- frontend/src/app/routes/resources.tsx | 18 +- frontend/src/app/routes/start-mapping.tsx | 50 +- frontend/src/assets/svgs/index.ts | 5 + frontend/src/components/errors/fallback.tsx | 6 +- .../components/errors/under-construction.tsx | 6 +- .../landing/about-fair/about-fair.tsx | 9 +- .../landing/core-features/core-features.tsx | 8 +- .../landing/core-values/core-values.tsx | 27 +- frontend/src/components/landing/cta/cta.tsx | 15 +- .../src/components/landing/header/header.tsx | 18 +- frontend/src/components/landing/kpi/kpi.tsx | 11 +- .../components/landing/tagline/tagline.tsx | 12 +- .../src/components/layout/footer/footer.tsx | 75 ++- .../src/components/layout/navbar/nav-logo.tsx | 6 +- .../src/components/layout/navbar/navbar.tsx | 16 +- .../components/layout/navbar/user-profile.tsx | 9 +- .../map/controls/fit-to-bounds-control.tsx | 4 +- .../components/map/controls/layer-control.tsx | 2 +- .../src/components/map/layers/basemaps.tsx | 2 +- .../components/map/layers/open-aerial-map.tsx | 2 +- .../components/map/layers/tile-boundaries.tsx | 7 +- .../components/map/setups/setup-maplibre.ts | 2 +- .../components/map/setups/setup-terra-draw.ts | 2 +- .../shared/fair-process/fair-process.tsx | 20 +- frontend/src/components/shared/faqs/faqs.tsx | 12 +- .../src/components/shared/hot-tracking.tsx | 3 +- frontend/src/components/ui/navbar/navbar.tsx | 23 +- frontend/src/constants/common.ts | 21 - .../constants.ts => constants/config.ts} | 54 -- frontend/src/constants/general.ts | 22 + frontend/src/constants/index.ts | 2 + frontend/src/constants/routes.ts | 53 ++ .../constants/ui-contents/about-content.ts | 2 +- frontend/src/constants/ui-contents/index.ts | 1 + .../constants/ui-contents/learn-content.ts | 2 +- .../src/constants/ui-contents/map-content.ts | 4 +- .../constants/ui-contents/models-content.ts | 326 ++++++++++- .../ui-contents/resources-content.ts | 2 +- .../constants/ui-contents/shared-content.ts | 194 +++++++ .../ui-contents/start-mapping-content.ts | 2 +- .../components/dialogs/file-upload-dialog.tsx | 14 +- .../model-details/model-description-input.tsx | 13 +- .../model-details/model-details.tsx | 20 +- .../model-details/model-name-input.tsx | 12 +- .../components/model-summary.tsx | 28 +- .../components/progress-buttons.tsx | 7 +- .../training-area/open-area-map.tsx | 19 +- .../training-area/training-area-item.tsx | 28 +- .../training-area/training-area-list.tsx | 13 +- .../training-area/training-area-map.tsx | 8 +- .../training-area/training-area.tsx | 17 +- .../training-dataset/create-new.tsx | 23 +- .../training-dataset/select-existing.tsx | 7 +- .../training-dataset/training-dataset.tsx | 15 +- .../training-settings-form.tsx | 43 +- .../training-settings/training-settings.tsx | 8 +- .../hooks/use-training-areas.ts | 2 +- .../dialogs/mobile-filters-dialog.tsx | 2 +- .../dialogs/model-details-update-dialog.tsx | 8 +- .../dialogs/model-enhancement-dialog.tsx | 17 +- .../components/dialogs/model-files-dialog.tsx | 8 +- .../dialogs/training-settings-dialog.tsx | 12 +- .../models/components/directory-tree.tsx | 18 +- .../components/filters/ordering-filter.tsx | 4 +- .../components/filters/search-filter.tsx | 5 +- .../src/features/models/components/header.tsx | 9 +- .../features/models/components/map-toggle.tsx | 4 +- .../components/maps/training-area-map.tsx | 10 +- .../features/models/components/model-card.tsx | 9 +- .../models/components/model-details-info.tsx | 26 +- .../models/components/model-details-popup.tsx | 28 +- .../components/model-details-properties.tsx | 62 ++- .../models/components/model-feedbacks.tsx | 4 +- .../models/components/model-files-button.tsx | 4 +- .../components/training-area-button.tsx | 4 +- .../components/training-area-drawer.tsx | 8 +- .../components/training-history-table.tsx | 26 +- .../src/features/models/layouts/table.tsx | 3 +- .../start-mapping/components/header.tsx | 6 +- .../components/logo-with-dropdown.tsx | 2 +- .../components/map/legend-control.tsx | 10 +- .../start-mapping/components/map/map.tsx | 11 +- .../components/mobile-drawer.tsx | 10 +- .../start-mapping/components/model-action.tsx | 8 +- .../components/model-details-button.tsx | 6 +- .../components/model-predictions-tracker.tsx | 6 +- .../components/model-settings.tsx | 22 +- .../start-mapping/components/popup.tsx | 28 +- frontend/src/hooks/use-layer-order.ts | 2 +- frontend/src/hooks/use-login.ts | 7 +- frontend/src/layouts/model-forms-layout.tsx | 16 +- frontend/src/layouts/root-layout.tsx | 2 +- frontend/src/services/api-client.ts | 7 +- frontend/src/types/ui-contents.ts | 487 ++++++++++++++++- frontend/src/utils/content.ts | 517 ------------------ .../{error-handler.ts => error-utils.ts} | 2 +- frontend/src/utils/general-utils.ts | 8 +- frontend/src/utils/index.ts | 5 +- 110 files changed, 1753 insertions(+), 1152 deletions(-) delete mode 100644 frontend/src/constants/common.ts rename frontend/src/{utils/constants.ts => constants/config.ts} (77%) create mode 100644 frontend/src/constants/general.ts create mode 100644 frontend/src/constants/routes.ts create mode 100644 frontend/src/constants/ui-contents/shared-content.ts delete mode 100644 frontend/src/utils/content.ts rename frontend/src/utils/{error-handler.ts => error-utils.ts} (92%) diff --git a/frontend/src/app/index.tsx b/frontend/src/app/index.tsx index 679d2d0a..da83bedb 100644 --- a/frontend/src/app/index.tsx +++ b/frontend/src/app/index.tsx @@ -6,7 +6,6 @@ import { } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { showErrorToast } from "@/utils"; -import axios from "axios"; export const App = () => { const queryClient = new QueryClient({ @@ -17,12 +16,6 @@ export const App = () => { if (query.state.data !== undefined) { showErrorToast(error); } - if (axios.isAxiosError(error)) { - // Server errors - if (error.response?.status && error.response.status >= 500) { - throw error; - } - } }, }), }); diff --git a/frontend/src/app/providers/auth-provider.tsx b/frontend/src/app/providers/auth-provider.tsx index 6b89013c..f8c76084 100644 --- a/frontend/src/app/providers/auth-provider.tsx +++ b/frontend/src/app/providers/auth-provider.tsx @@ -2,14 +2,13 @@ import { useLocalStorage, useSessionStorage } from "@/hooks/use-storage"; import { authService } from "@/services"; import { apiClient } from "@/services/api-client"; import { TUser } from "@/types/api"; +import { showErrorToast, showSuccessToast } from "@/utils"; import { + TOAST_NOTIFICATIONS, HOT_FAIR_LOCAL_STORAGE_ACCESS_TOKEN_KEY, HOT_FAIR_LOGIN_SUCCESSFUL_SESSION_KEY, HOT_FAIR_SESSION_REDIRECT_KEY, - showErrorToast, - showSuccessToast, -} from "@/utils"; -import { TOAST_NOTIFICATIONS } from "@/constants"; +} from "@/constants"; import React, { createContext, useContext, useState, useEffect } from "react"; type TAuthContext = { diff --git a/frontend/src/app/providers/models-provider.tsx b/frontend/src/app/providers/models-provider.tsx index 8c949a94..5cd4c262 100644 --- a/frontend/src/app/providers/models-provider.tsx +++ b/frontend/src/app/providers/models-provider.tsx @@ -1,9 +1,7 @@ import { BASE_MODELS, TrainingType, TrainingDatasetOption } from "@/enums"; import { useCreateTrainingDataset } from "@/features/model-creation/hooks/use-training-datasets"; +import { APPLICATION_ROUTES, MODELS_BASE, MODELS_ROUTES } from "@/constants"; import { - APPLICATION_ROUTES, - MODELS_BASE, - MODELS_ROUTES, showErrorToast, showSuccessToast, TMS_URL_REGEX_PATTERN, diff --git a/frontend/src/app/router.tsx b/frontend/src/app/router.tsx index 256a85a2..734afe69 100644 --- a/frontend/src/app/router.tsx +++ b/frontend/src/app/router.tsx @@ -1,4 +1,4 @@ -import { APPLICATION_ROUTES } from "@/utils"; +import { APPLICATION_ROUTES } from "@/constants"; import { Navigate, RouterProvider, diff --git a/frontend/src/app/routes/about.tsx b/frontend/src/app/routes/about.tsx index 6530ca48..9fe1bb9a 100644 --- a/frontend/src/app/routes/about.tsx +++ b/frontend/src/app/routes/about.tsx @@ -1,29 +1,29 @@ import { Header } from "@/components/shared"; import { Image } from "@/components/ui/image"; import { Head } from "@/components/seo"; -import { aboutPageContent } from "@/constants"; +import { ABOUT_PAGE_CONTENT } from "@/constants"; import { HOTTeamLandscape } from "@/assets/images"; import { AIIcon } from "@/assets/svgs"; export const AboutPage = () => { return (
    - -
    + +

    - {aboutPageContent.heroHeading.firstSegment}{" "} + {ABOUT_PAGE_CONTENT.heroHeading.firstSegment}{" "} - {aboutPageContent.heroHeading.secondSegment} + {ABOUT_PAGE_CONTENT.heroHeading.secondSegment} {" "} - {aboutPageContent.heroHeading.thirdSegment}{" "} + {ABOUT_PAGE_CONTENT.heroHeading.thirdSegment}{" "}

    {aboutPageContent.imageAlt} {

    - {aboutPageContent.bodyContent.firstParagraph} + {ABOUT_PAGE_CONTENT.bodyContent.firstParagraph}

    -

    {aboutPageContent.bodyContent.secondParagraph}

    +

    {ABOUT_PAGE_CONTENT.bodyContent.secondParagraph}

    AI Icon
    diff --git a/frontend/src/app/routes/account/models.tsx b/frontend/src/app/routes/account/models.tsx index be05bef6..04e8b01f 100644 --- a/frontend/src/app/routes/account/models.tsx +++ b/frontend/src/app/routes/account/models.tsx @@ -18,11 +18,11 @@ import { ModelListTableLayout, } from "@/features/models/layouts"; import { useDialog } from "@/hooks/use-dialog"; -import { APP_CONTENT } from "@/utils"; + import ModelNotFound from "@/features/models/components/model-not-found"; import { SEARCH_PARAMS } from "@/app/routes/models/models-list"; import { useAuth } from "@/app/providers/auth-provider"; -import { modelPagesContent } from "@/constants"; +import { MODELS_CONTENT } from "@/constants"; import { PAGE_LIMIT } from "@/components/shared"; export const UserModelsPage = () => { @@ -73,11 +73,11 @@ export const UserModelsPage = () => { updateQuery={updateQuery} disabled={isPending} /> - +
    {/* Filters */}
    @@ -130,7 +130,7 @@ export const UserModelsPage = () => {

    {data?.count}{" "} { - APP_CONTENT.models.modelsList.sortingAndPaginationSection + MODELS_CONTENT.models.modelsList.sortingAndPaginationSection .modelCountSuffix }

    diff --git a/frontend/src/app/routes/learn.tsx b/frontend/src/app/routes/learn.tsx index 356cc43e..c65fe562 100644 --- a/frontend/src/app/routes/learn.tsx +++ b/frontend/src/app/routes/learn.tsx @@ -8,7 +8,7 @@ import { Link } from "@/components/ui/link"; import { SHOELACE_SIZES } from "@/enums"; import { Head } from "@/components/seo"; import { useState } from "react"; -import { learnPageContent } from "@/constants"; +import { LEARN_PAGE_CONTENT } from "@/constants"; import { TGuide, TVideo } from "@/types"; import { JumbotronBackgroundImage } from "@/assets/images"; import { fAIrValues } from "@/assets/svgs"; @@ -16,27 +16,27 @@ import { fAIrValues } from "@/assets/svgs"; export const LearnPage = () => { return (
    - -
    + +

    - {learnPageContent.heroHeading.firstSegment}{" "} + {LEARN_PAGE_CONTENT.heroHeading.firstSegment}{" "} - {learnPageContent.heroHeading.secondSegment} + {LEARN_PAGE_CONTENT.heroHeading.secondSegment} {" "} - {learnPageContent.heroHeading.thirdSegment}{" "} + {LEARN_PAGE_CONTENT.heroHeading.thirdSegment}{" "} - {learnPageContent.heroHeading.fourthSegment} + {LEARN_PAGE_CONTENT.heroHeading.fourthSegment} {" "} - {learnPageContent.heroHeading.fifthSegment}{" "} + {LEARN_PAGE_CONTENT.heroHeading.fifthSegment}{" "} - {learnPageContent.heroHeading.sixthSegment} + {LEARN_PAGE_CONTENT.heroHeading.sixthSegment} {" "} - {learnPageContent.heroHeading.seventhSegment} + {LEARN_PAGE_CONTENT.heroHeading.seventhSegment}

    - {learnPageContent.heroDescription} + {LEARN_PAGE_CONTENT.heroDescription}

    @@ -50,18 +50,18 @@ export const LearnPage = () => {
    - +
    - {learnPageContent.guides.map((guide, id) => ( + {LEARN_PAGE_CONTENT.guides.map((guide, id) => ( ))}
    - +
    - {learnPageContent.videos.map((video, id) => ( + {LEARN_PAGE_CONTENT.videos.map((video, id) => ( ))}
    diff --git a/frontend/src/app/routes/models/confirmation.tsx b/frontend/src/app/routes/models/confirmation.tsx index 6bf76acc..8ed1b0aa 100644 --- a/frontend/src/app/routes/models/confirmation.tsx +++ b/frontend/src/app/routes/models/confirmation.tsx @@ -3,7 +3,7 @@ import { ModelFormConfirmation } from "@/assets/images"; import { Button } from "@/components/ui/button"; import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; -import { APPLICATION_ROUTES, MODEL_CREATION_CONTENT } from "@/utils"; +import { APPLICATION_ROUTES, MODELS_CONTENT } from "@/constants"; import ConfettiExplosion from "react-confetti-explosion"; import { useSearchParams } from "react-router-dom"; @@ -31,24 +31,26 @@ export const ModelConfirmationPage = () => { Model {modelId} is {isEditMode ? "Updated" : "Created"}!

    - {MODEL_CREATION_CONTENT.confirmation.description} + {MODELS_CONTENT.modelCreation.confirmation.description}

    diff --git a/frontend/src/app/routes/models/model-details.tsx b/frontend/src/app/routes/models/model-details.tsx index b0d9b7bd..7fbfa378 100644 --- a/frontend/src/app/routes/models/model-details.tsx +++ b/frontend/src/app/routes/models/model-details.tsx @@ -11,7 +11,6 @@ import { ModelFilesDialog } from "@/features/models/components/dialogs"; import { ModelDetailsSkeleton } from "@/features/models/components/skeletons"; import { useModelDetails } from "@/features/models/hooks/use-models"; import { useDialog } from "@/hooks/use-dialog"; -import { APP_CONTENT } from "@/utils"; import { useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; import { Image } from "@/components/ui/image"; @@ -21,8 +20,8 @@ import { useAuth } from "@/app/providers/auth-provider"; import { TrainingAreaDrawer } from "@/features/models/components/training-area-drawer"; import { useGetTrainingDataset } from "@/features/models/hooks/use-dataset"; import { TrainingInProgressImage } from "@/assets/images"; - import { handleErrorNavigation } from "@/utils"; +import { MODELS_CONTENT } from "@/constants"; export const ModelDetailsPage = () => { const { id } = useParams<{ id: string }>(); @@ -103,7 +102,7 @@ export const ModelDetailsPage = () => { trainingDataset={trainingDataset as TTrainingDataset} /> {!data?.published_training ? (
    @@ -126,7 +125,7 @@ export const ModelDetailsPage = () => {
    { {/* mobile */}
    {

    {data?.count}{" "} { - APP_CONTENT.models.modelsList.sortingAndPaginationSection + MODELS_CONTENT.models.modelsList.sortingAndPaginationSection .modelCountSuffix }

    diff --git a/frontend/src/app/routes/not-found.tsx b/frontend/src/app/routes/not-found.tsx index c6f89d40..2fad4813 100644 --- a/frontend/src/app/routes/not-found.tsx +++ b/frontend/src/app/routes/not-found.tsx @@ -1,6 +1,6 @@ import { Button } from "@/components/ui/button"; -import { APPLICATION_ROUTES } from "@/utils/constants"; -import { APP_CONTENT } from "@/utils"; +import { APPLICATION_ROUTES, SHARED_CONTENT } from "@/constants"; + import { useLocation, useNavigate } from "react-router-dom"; import { Head } from "@/components/seo"; @@ -23,14 +23,14 @@ export const PageNotFound = () => {

    - {APP_CONTENT.pageNotFound.messages.constant}{" "} + {SHARED_CONTENT.pageNotFound.messages.constant}{" "} {location.state?.error ? location.state?.error : modelNotFound - ? APP_CONTENT.pageNotFound.messages.modelNotFound + ? SHARED_CONTENT.pageNotFound.messages.modelNotFound : trainingDatasetNotFound - ? APP_CONTENT.pageNotFound.messages.trainingDatasetNotFound - : APP_CONTENT.pageNotFound.messages.pageNotFound} + ? SHARED_CONTENT.pageNotFound.messages.trainingDatasetNotFound + : SHARED_CONTENT.pageNotFound.messages.pageNotFound}

    @@ -79,10 +79,11 @@ export const PageNotFound = () => { className="max-w-[300px]" > {modelNotFound - ? APP_CONTENT.pageNotFound.actionButtons.modelNotFound + ? SHARED_CONTENT.pageNotFound.actionButtons.modelNotFound : trainingDatasetNotFound - ? APP_CONTENT.pageNotFound.actionButtons.trainingDatasetNotFound - : APP_CONTENT.pageNotFound.actionButtons.pageNotFound} + ? SHARED_CONTENT.pageNotFound.actionButtons + .trainingDatasetNotFound + : SHARED_CONTENT.pageNotFound.actionButtons.pageNotFound}

    diff --git a/frontend/src/app/routes/protected-route.tsx b/frontend/src/app/routes/protected-route.tsx index 2522c498..1bf83c84 100644 --- a/frontend/src/app/routes/protected-route.tsx +++ b/frontend/src/app/routes/protected-route.tsx @@ -1,9 +1,10 @@ import { ShieldIcon } from "@/components/ui/icons"; import { useAuth } from "@/app/providers/auth-provider"; -import { APP_CONTENT } from "@/utils"; + import { Button } from "@/components/ui/button"; import { useLogin } from "@/hooks/use-login"; import { Head } from "@/components/seo"; +import { SHARED_CONTENT } from "@/constants"; type ProtectedRouteProps = { children: React.ReactNode; @@ -24,10 +25,10 @@ export const ProtectedRoute: React.FC = ({ children }) => {

    - {APP_CONTENT.protectedPage.messageTitle} + {SHARED_CONTENT.protectedPage.messageTitle}

    - {APP_CONTENT.protectedPage.messageParagraph} + {SHARED_CONTENT.protectedPage.messageParagraph}

    @@ -38,8 +39,8 @@ export const ProtectedRoute: React.FC = ({ children }) => { spinner={loading} > {loading - ? APP_CONTENT.loginButtonLoading - : APP_CONTENT.protectedPage.ctaButton} + ? SHARED_CONTENT.loginButtonLoading + : SHARED_CONTENT.protectedPage.ctaButton}
    diff --git a/frontend/src/app/routes/resources.tsx b/frontend/src/app/routes/resources.tsx index 0947b176..2364117b 100644 --- a/frontend/src/app/routes/resources.tsx +++ b/frontend/src/app/routes/resources.tsx @@ -1,7 +1,7 @@ import { FAQs, SectionHeader } from "@/components/shared"; import { Head } from "@/components/seo"; import { Header } from "@/components/shared"; -import { resourcesPageContent } from "@/constants"; +import { RESOURCES_PAGE_CONTENT } from "@/constants"; import { TArticle } from "@/types"; import { Image } from "@/components/ui/image"; import { ChevronDownIcon } from "@/components/ui/icons"; @@ -11,26 +11,26 @@ import { truncateString } from "@/utils"; export const ResourcesPage = () => { return (
    - -
    + +

    - {resourcesPageContent.hero.firstSegment}{" "} + {RESOURCES_PAGE_CONTENT.hero.firstSegment}{" "} - {resourcesPageContent.hero.secondSegment} + {RESOURCES_PAGE_CONTENT.hero.secondSegment} {" "} - {resourcesPageContent.hero.thirdSegment}{" "} + {RESOURCES_PAGE_CONTENT.hero.thirdSegment}{" "}

    - +
    - +
    - {resourcesPageContent.articles.articles.map((article, id) => ( + {RESOURCES_PAGE_CONTENT.articles.articles.map((article, id) => ( ))}
    diff --git a/frontend/src/app/routes/start-mapping.tsx b/frontend/src/app/routes/start-mapping.tsx index 524db0c1..6dea084a 100644 --- a/frontend/src/app/routes/start-mapping.tsx +++ b/frontend/src/app/routes/start-mapping.tsx @@ -12,23 +12,26 @@ import { StartMappingMobileDrawer, } from "@/features/start-mapping/components"; import { useGetTMSTileJSON } from "@/features/model-creation/hooks/use-tms-tilejson"; +import { APPLICATION_ROUTES } from "@/constants"; import { - ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, - ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, - APPLICATION_ROUTES, extractTileJSONURL, geoJSONDowloader, - MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION, openInJOSM, - PREDICTION_API_FILE_EXTENSIONS, - REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, - REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, showSuccessToast, } from "@/utils"; import { BASE_MODELS } from "@/enums"; -import { startMappingPageContent, TOAST_NOTIFICATIONS } from "@/constants"; +import { + START_MAPPING_PAGE_CONTENT, + TOAST_NOTIFICATIONS, + PREDICTION_API_FILE_EXTENSIONS, + REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + REJECTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + MIN_ZOOM_LEVEL_FOR_START_MAPPING_PREDICTION, + ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, + ACCEPTED_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, + ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, + ALL_MODEL_PREDICTIONS_OUTLINE_LAYER_ID, +} from "@/constants"; import { useMapInstance } from "@/hooks/use-map-instance"; import useScreenSize from "@/hooks/use-screen-size"; import { ModelDetailsPopUp } from "@/features/models/components"; @@ -167,7 +170,7 @@ export const StartMappingPage = () => { ? [ { value: - startMappingPageContent.map.controls.legendControl + START_MAPPING_PAGE_CONTENT.map.controls.legendControl .acceptedPredictions, subLayers: [ ACCEPTED_MODEL_PREDICTIONS_FILL_LAYER_ID, @@ -180,7 +183,7 @@ export const StartMappingPage = () => { ? [ { value: - startMappingPageContent.map.controls.legendControl + START_MAPPING_PAGE_CONTENT.map.controls.legendControl .rejectedPredictions, subLayers: [ REJECTED_MODEL_PREDICTIONS_FILL_LAYER_ID, @@ -193,7 +196,7 @@ export const StartMappingPage = () => { ? [ { value: - startMappingPageContent.map.controls.legendControl + START_MAPPING_PAGE_CONTENT.map.controls.legendControl .predictionResults, subLayers: [ ALL_MODEL_PREDICTIONS_FILL_LAYER_ID, @@ -251,30 +254,33 @@ export const StartMappingPage = () => { const downloadOptions: TDownloadOptions = [ { - name: startMappingPageContent.buttons.download.options.allFeatures, - value: startMappingPageContent.buttons.download.options.allFeatures, + name: START_MAPPING_PAGE_CONTENT.buttons.download.options.allFeatures, + value: START_MAPPING_PAGE_CONTENT.buttons.download.options.allFeatures, onClick: handleAllFeaturesDownload, showOnMobile: true, }, { - name: startMappingPageContent.buttons.download.options.acceptedFeatures, - value: startMappingPageContent.buttons.download.options.acceptedFeatures, + name: START_MAPPING_PAGE_CONTENT.buttons.download.options + .acceptedFeatures, + value: + START_MAPPING_PAGE_CONTENT.buttons.download.options.acceptedFeatures, onClick: handleAcceptedFeaturesDownload, showOnMobile: true, }, { - name: startMappingPageContent.buttons.download.options + name: START_MAPPING_PAGE_CONTENT.buttons.download.options .openAllFeaturesInJOSM, value: - startMappingPageContent.buttons.download.options.openAllFeaturesInJOSM, + START_MAPPING_PAGE_CONTENT.buttons.download.options + .openAllFeaturesInJOSM, onClick: handleAllFeaturesDownloadToJOSM, showOnMobile: false, }, { - name: startMappingPageContent.buttons.download.options + name: START_MAPPING_PAGE_CONTENT.buttons.download.options .openAcceptedFeaturesInJOSM, value: - startMappingPageContent.buttons.download.options + START_MAPPING_PAGE_CONTENT.buttons.download.options .openAcceptedFeaturesInJOSM, onClick: handleAcceptedFeaturesDownloadToJOSM, showOnMobile: false, @@ -295,7 +301,7 @@ export const StartMappingPage = () => { return ( <> - + {/* Mobile dialog */}
    { return ( @@ -7,7 +7,7 @@ export const MainErrorFallback = () => {

    - {APP_CONTENT.errorBoundary.title} + {SHARED_CONTENT.errorBoundary.title}

    @@ -16,7 +16,7 @@ export const MainErrorFallback = () => { onClick={() => window.location.reload()} className="max-w-80" > - {APP_CONTENT.errorBoundary.button} + {SHARED_CONTENT.errorBoundary.button}
    diff --git a/frontend/src/components/errors/under-construction.tsx b/frontend/src/components/errors/under-construction.tsx index bc0a578e..9a243410 100644 --- a/frontend/src/components/errors/under-construction.tsx +++ b/frontend/src/components/errors/under-construction.tsx @@ -1,12 +1,12 @@ import { Button } from "@/components/ui/button"; -import { APP_CONTENT } from "@/utils"; +import { SHARED_CONTENT } from "@/constants"; export const PageUnderConstruction = () => { return (

    - {APP_CONTENT.construction.message} + {SHARED_CONTENT.construction.message}

    @@ -15,7 +15,7 @@ export const PageUnderConstruction = () => { onClick={() => window.location.assign(window.location.origin)} className="max-w-80" > - {APP_CONTENT.construction.button} + {SHARED_CONTENT.construction.button}
    ); diff --git a/frontend/src/components/landing/about-fair/about-fair.tsx b/frontend/src/components/landing/about-fair/about-fair.tsx index 659730c3..efce2bd0 100644 --- a/frontend/src/components/landing/about-fair/about-fair.tsx +++ b/frontend/src/components/landing/about-fair/about-fair.tsx @@ -1,14 +1,17 @@ import styles from "./about-fair.module.css"; -import { APP_CONTENT } from "@/utils/content"; + import { Image } from "@/components/ui/image"; import { AIIcon } from "@/assets/svgs"; +import { SHARED_CONTENT } from "@/constants"; export const WhatIsFAIR = () => { return (
    -

    {APP_CONTENT.homepage.aboutTitle}

    -

    {APP_CONTENT.homepage.aboutContent}

    +

    {SHARED_CONTENT.homepage.aboutTitle}

    +

    + {SHARED_CONTENT.homepage.aboutContent} +

    AI Icon diff --git a/frontend/src/components/landing/core-features/core-features.tsx b/frontend/src/components/landing/core-features/core-features.tsx index 1abc8763..d6e46f05 100644 --- a/frontend/src/components/landing/core-features/core-features.tsx +++ b/frontend/src/components/landing/core-features/core-features.tsx @@ -1,7 +1,7 @@ import { GuageIcon, LoopIcon, TimerIcon } from "@/components/ui/icons"; import styles from "./core-features.module.css"; import { IconProps } from "@/types"; -import { APP_CONTENT } from "@/utils/content"; +import { SHARED_CONTENT } from "@/constants"; type TCoreFeatures = { title: string; @@ -9,15 +9,15 @@ type TCoreFeatures = { }; const coreFeatures: TCoreFeatures[] = [ { - title: APP_CONTENT.homepage.coreFeatures.featureOne, + title: SHARED_CONTENT.homepage.coreFeatures.featureOne, icon: TimerIcon, }, { - title: APP_CONTENT.homepage.coreFeatures.featureTwo, + title: SHARED_CONTENT.homepage.coreFeatures.featureTwo, icon: GuageIcon, }, { - title: APP_CONTENT.homepage.coreFeatures.featureThree, + title: SHARED_CONTENT.homepage.coreFeatures.featureThree, icon: LoopIcon, }, ]; diff --git a/frontend/src/components/landing/core-values/core-values.tsx b/frontend/src/components/landing/core-values/core-values.tsx index 81c3ab39..6931868c 100644 --- a/frontend/src/components/landing/core-values/core-values.tsx +++ b/frontend/src/components/landing/core-values/core-values.tsx @@ -1,14 +1,15 @@ import styles from "./core-values.module.css"; -import { APP_CONTENT } from "@/utils/content"; + import { Image } from "@/components/ui/image"; import { HOTTeam, MapathonOngoing } from "@/assets/images"; import { DashedLineConnector } from "@/assets/svgs"; +import { SHARED_CONTENT } from "@/constants"; export const Corevalues = () => { return (
    -

    {APP_CONTENT.homepage.coreValues.sectionTitle.firstSegment}

    +

    {SHARED_CONTENT.homepage.coreValues.sectionTitle.firstSegment}

    { />

    - {APP_CONTENT.homepage.coreValues.sectionTitle.secondSegment} + {SHARED_CONTENT.homepage.coreValues.sectionTitle.secondSegment}

    - {APP_CONTENT.homepage.coreValues.sectionTitle.thirdSegment} + {SHARED_CONTENT.homepage.coreValues.sectionTitle.thirdSegment}

    - {APP_CONTENT.homepage.coreValues.sectionTitle.fourthSegment} + {SHARED_CONTENT.homepage.coreValues.sectionTitle.fourthSegment}

    - {APP_CONTENT.homepage.coreValues.sectionTitle.fifthSegment} + {SHARED_CONTENT.homepage.coreValues.sectionTitle.fifthSegment}

    @@ -57,13 +58,13 @@ export const Corevalues = () => { {/* Community Driven */}
    -

    {APP_CONTENT.homepage.coreValues.community.title}

    -

    {APP_CONTENT.homepage.coreValues.community.description}

    +

    {SHARED_CONTENT.homepage.coreValues.community.title}

    +

    {SHARED_CONTENT.homepage.coreValues.community.description}

    {APP_CONTENT.homepage.coreValues.community.title} {/* The rectangles */} @@ -84,15 +85,17 @@ export const Corevalues = () => { {/* Humans not replaced */}
    -

    {APP_CONTENT.homepage.coreValues.humansNotReplaced.title}

    +

    + {SHARED_CONTENT.homepage.coreValues.humansNotReplaced.title} +

    - {APP_CONTENT.homepage.coreValues.humansNotReplaced.description} + {SHARED_CONTENT.homepage.coreValues.humansNotReplaced.description}

    {APP_CONTENT.homepage.coreValues.humansNotReplaced.title} {/* The rectangles */} diff --git a/frontend/src/components/landing/cta/cta.tsx b/frontend/src/components/landing/cta/cta.tsx index e0c936f3..ca00ba40 100644 --- a/frontend/src/components/landing/cta/cta.tsx +++ b/frontend/src/components/landing/cta/cta.tsx @@ -1,27 +1,28 @@ import { Button } from "@/components/ui/button/"; import styles from "./cta.module.css"; -import { APP_CONTENT } from "@/utils"; + import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; import { HOTTeamTwo } from "@/assets/images"; +import { SHARED_CONTENT } from "@/constants"; export const CallToAction = () => { return (
    -

    {APP_CONTENT.homepage.callToAction.title}

    -

    {APP_CONTENT.homepage.callToAction.paragraph}

    +

    {SHARED_CONTENT.homepage.callToAction.title}

    +

    {SHARED_CONTENT.homepage.callToAction.paragraph}

    @@ -29,7 +30,7 @@ export const CallToAction = () => {
    {APP_CONTENT.homepage.callToAction.ctaButton} {/* The rectangles */} diff --git a/frontend/src/components/landing/header/header.tsx b/frontend/src/components/landing/header/header.tsx index 29e1bf5d..f6f76270 100644 --- a/frontend/src/components/landing/header/header.tsx +++ b/frontend/src/components/landing/header/header.tsx @@ -1,10 +1,10 @@ import { Button } from "@/components/ui/button"; import styles from "@/components/landing/header/header.module.css"; import BackgroundImage from "@/assets/images/header_bg.jpg"; -import { APP_CONTENT } from "@/utils/content"; + import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; -import { APPLICATION_ROUTES } from "@/utils"; +import { APPLICATION_ROUTES, SHARED_CONTENT } from "@/constants"; export const Header = () => { return ( @@ -12,26 +12,26 @@ export const Header = () => {
    -

    {APP_CONTENT.homepage.jumbotronTitle}

    -

    {APP_CONTENT.homepage.jumbotronHeadline}

    +

    {SHARED_CONTENT.homepage.jumbotronTitle}

    +

    {SHARED_CONTENT.homepage.jumbotronHeadline}

    @@ -39,7 +39,7 @@ export const Header = () => {
    {APP_CONTENT.homepage.jumbotronImageAlt} diff --git a/frontend/src/components/landing/kpi/kpi.tsx b/frontend/src/components/landing/kpi/kpi.tsx index 515d32ef..735ff7d6 100644 --- a/frontend/src/components/landing/kpi/kpi.tsx +++ b/frontend/src/components/landing/kpi/kpi.tsx @@ -1,8 +1,7 @@ -import { APP_CONTENT, KPI_STATS_CACHE_TIME_MS } from "@/utils"; import styles from "./kpi.module.css"; import { API_ENDPOINTS, apiClient } from "@/services"; import { useQuery } from "@tanstack/react-query"; - +import { KPI_STATS_CACHE_TIME_MS, SHARED_CONTENT } from "@/constants"; type TKPIS = { figure?: number; label: string; @@ -41,19 +40,19 @@ export const Kpi = () => { const KPIs: TKPIS = [ { figure: data?.total_models_published ?? 0, - label: APP_CONTENT.homepage.kpi.publishedAIModels, + label: SHARED_CONTENT.homepage.kpi.publishedAIModels, }, { figure: data?.total_registered_users ?? 0, - label: APP_CONTENT.homepage.kpi.totalUsers, + label: SHARED_CONTENT.homepage.kpi.totalUsers, }, { figure: data?.total_feedback_labels ?? 0, - label: APP_CONTENT.homepage.kpi.humanFeedback, + label: SHARED_CONTENT.homepage.kpi.humanFeedback, }, { figure: data?.total_accepted_predictions ?? 0, - label: APP_CONTENT.homepage.kpi.acceptedPrediction, + label: SHARED_CONTENT.homepage.kpi.acceptedPrediction, }, ]; diff --git a/frontend/src/components/landing/tagline/tagline.tsx b/frontend/src/components/landing/tagline/tagline.tsx index 041e90bb..06b9a9e7 100644 --- a/frontend/src/components/landing/tagline/tagline.tsx +++ b/frontend/src/components/landing/tagline/tagline.tsx @@ -1,17 +1,17 @@ -import { APP_CONTENT } from "@/utils/content"; +import { SHARED_CONTENT } from "@/constants"; import styles from "./tagline.module.css"; export const TaglineBanner = () => { return (

    - {APP_CONTENT.homepage.tagline.firstSegment} - {APP_CONTENT.homepage.tagline.secondSegment} + {SHARED_CONTENT.homepage.tagline.firstSegment} + {SHARED_CONTENT.homepage.tagline.secondSegment} - {APP_CONTENT.homepage.tagline.thirdSegment} + {SHARED_CONTENT.homepage.tagline.thirdSegment} - {APP_CONTENT.homepage.tagline.fourthSegment} - {APP_CONTENT.homepage.tagline.fifthSegment} + {SHARED_CONTENT.homepage.tagline.fourthSegment} + {SHARED_CONTENT.homepage.tagline.fifthSegment}

    ); diff --git a/frontend/src/components/layout/footer/footer.tsx b/frontend/src/components/layout/footer/footer.tsx index 25c9b3ca..99a6a3f4 100644 --- a/frontend/src/components/layout/footer/footer.tsx +++ b/frontend/src/components/layout/footer/footer.tsx @@ -1,38 +1,40 @@ import { CreativeCommonsBadge } from "@/assets/images"; -import FacebookLogo from "@/assets/svgs/socials/facebook_logo.svg"; -import GitHubLogo from "@/assets/svgs/socials/github_logo.svg"; -import XLogo from "@/assets/svgs/socials/x_logo.svg"; -import InstagramLogo from "@/assets/svgs/socials/instagram_logo.svg"; -import YoutTubeLogo from "@/assets/svgs/socials/youtube_logo.svg"; -import { APP_CONTENT } from "@/utils/content"; import { Image } from "@/components/ui/image"; import { Link } from "@/components/ui/link"; +import { + FacebookIcon, + GitHubIcon, + InstagramIcon, + XIcon, + YouTubeIcon, +} from "@/assets/svgs"; +import { SHARED_CONTENT } from "@/constants"; const socials = [ { name: "Facebook", - url: "#", - logo: FacebookLogo, + url: "https://www.facebook.com/hotosm", + logo: FacebookIcon, }, { name: "X", - url: "#", - logo: XLogo, + url: "https://twitter.com/hotosm/", + logo: XIcon, }, { name: "GitHub", - url: "#", - logo: GitHubLogo, + url: "https://github.com/hotosm/fair", + logo: GitHubIcon, }, { name: "YouTube", - url: "#", - logo: YoutTubeLogo, + url: "https://www.youtube.com/user/hotosm", + logo: YouTubeIcon, }, { name: "Instagram", - url: "#", - logo: InstagramLogo, + url: "https://www.instagram.com/hot.osm/", + logo: InstagramIcon, }, ]; export const Footer = () => { @@ -41,11 +43,11 @@ export const Footer = () => {
    -

    {APP_CONTENT.footer.title}

    +

    {SHARED_CONTENT.footer.title}

      - {APP_CONTENT.footer.siteMap.groupOne.map((route, id) => ( + {SHARED_CONTENT.footer.siteMap.groupOne.map((route, id) => (
    • {
      - {APP_CONTENT.footer.siteMap.groupTwo.map((route, id) => ( + {SHARED_CONTENT.footer.siteMap.groupTwo.map((route, id) => (
    • { />
    -

    {APP_CONTENT.footer.copyright.firstSegment}

    -

    {APP_CONTENT.footer.copyright.secondSegment}

    +

    {SHARED_CONTENT.footer.copyright.firstSegment}

    +

    {SHARED_CONTENT.footer.copyright.secondSegment}

    @@ -107,17 +109,38 @@ export const Footer = () => { ))} -

    {APP_CONTENT.footer.socials.ctaText}

    + +

    {SHARED_CONTENT.footer.socials.ctaText}

    +

    - {APP_CONTENT.footer.madeWithLove.firstSegment} - {APP_CONTENT.footer.madeWithLove.secondSegment} - {APP_CONTENT.footer.madeWithLove.thirdSegment} - {APP_CONTENT.footer.madeWithLove.fourthSegment} + {SHARED_CONTENT.footer.madeWithLove.firstSegment} + + {SHARED_CONTENT.footer.madeWithLove.secondSegment} + + {SHARED_CONTENT.footer.madeWithLove.thirdSegment} + + {SHARED_CONTENT.footer.madeWithLove.fourthSegment} +

    diff --git a/frontend/src/components/layout/navbar/nav-logo.tsx b/frontend/src/components/layout/navbar/nav-logo.tsx index 151fb7e3..b42061f3 100644 --- a/frontend/src/components/layout/navbar/nav-logo.tsx +++ b/frontend/src/components/layout/navbar/nav-logo.tsx @@ -1,4 +1,4 @@ -import { APP_CONTENT, APPLICATION_ROUTES } from "@/utils"; +import { APPLICATION_ROUTES, SHARED_CONTENT } from "@/constants"; import { Image } from "@/components/ui/image"; import { useNavigate } from "react-router-dom"; import { BrandLogo } from "@/assets/svgs"; @@ -21,12 +21,12 @@ export const NavLogo = ({ return ( )}
    @@ -66,16 +66,16 @@ export const NavBar = () => { spinner={loading} > {loading - ? APP_CONTENT.loginButtonLoading - : APP_CONTENT.navbar.loginButton} + ? SHARED_CONTENT.loginButtonLoading + : SHARED_CONTENT.navbar.loginButton} )}
    )}
    @@ -65,16 +66,16 @@ export const NavBar = () => { spinner={loading} > {loading - ? APP_CONTENT.loginButtonLoading - : APP_CONTENT.navbar.loginButton} + ? SHARED_CONTENT.loginButtonLoading + : SHARED_CONTENT.navbar.loginButton} )}
    diff --git a/frontend/src/features/model-creation/components/model-details/model-description-input.tsx b/frontend/src/features/model-creation/components/model-details/model-description-input.tsx index b1c40a7e..893277e9 100644 --- a/frontend/src/features/model-creation/components/model-details/model-description-input.tsx +++ b/frontend/src/features/model-creation/components/model-details/model-description-input.tsx @@ -3,7 +3,7 @@ import { MODEL_CREATION_FORM_NAME, } from "@/app/providers/models-provider"; import { TextArea } from "@/components/ui/form"; -import { MODEL_CREATION_CONTENT } from "@/utils"; +import { MODELS_CONTENT } from "@/constants"; const ModelDescriptionFormInput = ({ handleChange, @@ -15,16 +15,19 @@ const ModelDescriptionFormInput = ({ return (