From 78c9cce42f81b49f5a6844759d69eabb4e9b0fae Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 09:40:43 +1100 Subject: [PATCH 01/11] Refactor and simply code, undo change which create problem --- src/components/map/mapbox/Map.tsx | 7 +- .../map/mapbox/controls/DrawControl.tsx | 97 -------------- .../map/mapbox/controls/DrawRectControl.tsx | 126 ++++++++++++++++++ .../mapbox/controls/DrawRectangleControl.tsx | 39 ------ .../map/mapbox/controls/ScaleControl.tsx | 4 +- .../map/mapbox/controls/menu/MenuControl.tsx | 28 ++-- .../tab-panels/AbstractAndDownloadPanel.tsx | 9 +- 7 files changed, 142 insertions(+), 168 deletions(-) delete mode 100644 src/components/map/mapbox/controls/DrawControl.tsx create mode 100644 src/components/map/mapbox/controls/DrawRectControl.tsx delete mode 100644 src/components/map/mapbox/controls/DrawRectangleControl.tsx diff --git a/src/components/map/mapbox/Map.tsx b/src/components/map/mapbox/Map.tsx index f8802aee..828354c7 100644 --- a/src/components/map/mapbox/Map.tsx +++ b/src/components/map/mapbox/Map.tsx @@ -32,12 +32,12 @@ const styles = [ { id: "1", name: "Street map (MapBox)", - style: "mapbox://styles/mapbox/streets-v11", + style: "mapbox://styles/mapbox/streets-v12", }, { id: "2", name: "Topographic map (MapBox)", - style: "mapbox://styles/mapbox/outdoors-v11", + style: "mapbox://styles/mapbox/outdoors-v12", }, { id: "3", @@ -109,7 +109,7 @@ const ReactMap = ({ } // Create new map instance - const newMap = new Map({ + return new Map({ container: panelId, accessToken: import.meta.env.VITE_MAPBOX_ACCESS_TOKEN, style: styles[MapDefaultConfig.DEFAULT_STYLE].style, @@ -118,7 +118,6 @@ const ReactMap = ({ testMode: import.meta.env.MODE === "dev", localIdeographFontFamily: "'Noto Sans', 'Noto Sans CJK SC', sans-serif", }); - return newMap; } catch (err) { console.log("Map initialization failed:", err); } diff --git a/src/components/map/mapbox/controls/DrawControl.tsx b/src/components/map/mapbox/controls/DrawControl.tsx deleted file mode 100644 index 6198ae13..00000000 --- a/src/components/map/mapbox/controls/DrawControl.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; -import { Polygon } from "geojson"; -import * as turf from "@turf/turf"; -import { Map } from "mapbox-gl"; -import MapboxDraw from "@mapbox/mapbox-gl-draw"; - -import "mapbox-gl/dist/mapbox-gl.css"; -import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; -import DrawRectangleControl from "./DrawRectangleControl"; -import { - BBoxCondition, - DownloadConditionType, - IDownloadCondition, -} from "../../../../pages/detail-page/context/DownloadDefinitions"; -import _ from "lodash"; - -interface DrawControlProps { - map: Map | undefined | null; - setDownloadConditions: ( - type: DownloadConditionType, - conditions: IDownloadCondition[] - ) => void; - draw: MapboxDraw; -} - -const DrawControl: React.FC = ({ - map, - setDownloadConditions, - draw, -}) => { - const [open, setOpen] = useState(false); - - const anchorRef = useRef(null); - const popperRef = useRef(null); - - const handleClickOutside = useCallback((event: MouseEvent) => { - if (!popperRef.current || !anchorRef.current) { - return; - } - if (!popperRef.current.contains(event.target as Node)) { - setOpen(false); - } - }, []); - - useEffect(() => { - if (open) { - document.addEventListener("mousedown", handleClickOutside); - } else { - document.removeEventListener("mousedown", handleClickOutside); - } - - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [handleClickOutside, open]); - - useEffect(() => { - const updateArea = () => { - const features = draw.getAll().features; - const bboxes: BBoxCondition[] = - features - ?.filter((feature) => feature.geometry.type === "Polygon") - .map((feature) => { - const polygon = feature.geometry as Polygon; - const bbox = turf.bbox(polygon); - const id = _.toString(feature.id); - return new BBoxCondition(bbox, id); - }) || []; - setDownloadConditions(DownloadConditionType.BBOX, bboxes); - }; - - if (map) { - map.addControl(new DrawRectangleControl(draw), "top-right"); - map.addControl(draw); - - map.on("draw.create", updateArea); - map.on("draw.delete", updateArea); - map.on("draw.update", updateArea); - } - - return () => { - if (!map) return; - try { - map.off("draw.create", updateArea); - map.off("draw.delete", updateArea); - map.off("draw.update", updateArea); - map.removeControl(draw); - } catch (ignored) { - /* can be ignored */ - } - }; - }, [draw, map, setDownloadConditions]); - - return <>; -}; - -export default DrawControl; diff --git a/src/components/map/mapbox/controls/DrawRectControl.tsx b/src/components/map/mapbox/controls/DrawRectControl.tsx new file mode 100644 index 00000000..978c80dc --- /dev/null +++ b/src/components/map/mapbox/controls/DrawRectControl.tsx @@ -0,0 +1,126 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { Polygon } from "geojson"; +import * as turf from "@turf/turf"; +import MapboxDraw from "@mapbox/mapbox-gl-draw"; + +import "mapbox-gl/dist/mapbox-gl.css"; +import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; +import { + BBoxCondition, + DownloadConditionType, + IDownloadCondition, +} from "../../../../pages/detail-page/context/DownloadDefinitions"; +import _ from "lodash"; +import { IconButton } from "@mui/material"; +import BBoxIcon from "../../../icon/BBoxIcon"; +import DrawRectangle from "./DrawRectangle"; +import { ControlProps } from "./menu/Definition"; + +interface DrawControlProps extends ControlProps { + setDownloadConditions: ( + type: DownloadConditionType, + conditions: IDownloadCondition[] + ) => void; + draw: MapboxDraw; +} + +const DrawRectControl: React.FC = ({ + map, + setDownloadConditions, + draw, +}) => { + const [open, setOpen] = useState(false); + const mapDraw = useMemo( + () => + new MapboxDraw({ + displayControlsDefault: false, + controls: { + trash: true, + }, + defaultMode: "simple_select", + modes: { + ...MapboxDraw.modes, + draw_rectangle: DrawRectangle, + }, + }), + [] + ); + + const anchorRef = useRef(null); + const popperRef = useRef(null); + + const handleClickOutside = useCallback((event: MouseEvent) => { + if (!popperRef.current || !anchorRef.current) { + return; + } + if (!popperRef.current.contains(event.target as Node)) { + setOpen(false); + } + }, []); + + useEffect(() => { + if (open) { + document.addEventListener("mousedown", handleClickOutside); + } else { + document.removeEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [handleClickOutside, open]); + + useEffect(() => { + if (map) { + const updateArea = () => { + const features = mapDraw.getAll().features; + const bboxes: BBoxCondition[] = + features + ?.filter((feature) => feature.geometry.type === "Polygon") + .map((feature) => { + const polygon = feature.geometry as Polygon; + const bbox = turf.bbox(polygon); + const id = _.toString(feature.id); + return new BBoxCondition(bbox, id); + }) || []; + setDownloadConditions(DownloadConditionType.BBOX, bboxes); + }; + + map.addControl(mapDraw); + map.on("draw.create", updateArea); + map.on("draw.delete", updateArea); + map.on("draw.update", updateArea); + + return () => { + try { + map.off("draw.create", updateArea); + map.off("draw.delete", updateArea); + map.off("draw.update", updateArea); + map.removeControl(mapDraw); + } catch (ignored) { + /* can be ignored */ + } + }; + } + }, [mapDraw, map, setDownloadConditions]); + + return ( + mapDraw.changeMode("draw_rectangle")} + sx={{ paddingTop: "3px !important" }} + > + + + ); +}; + +export default DrawRectControl; diff --git a/src/components/map/mapbox/controls/DrawRectangleControl.tsx b/src/components/map/mapbox/controls/DrawRectangleControl.tsx deleted file mode 100644 index e83e6c27..00000000 --- a/src/components/map/mapbox/controls/DrawRectangleControl.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { IControl, Map } from "mapbox-gl"; -import MapboxDraw from "@mapbox/mapbox-gl-draw"; -import { createRoot, Root } from "react-dom/client"; -import BBoxIcon from "../../../icon/BBoxIcon"; -import { safeRemoveControl } from "../../../../utils/MapUtils"; - -class DrawRectangleControl implements IControl { - private draw: MapboxDraw; - private root: Root | null = null; - private container: HTMLDivElement | null = null; - private map: Map | undefined | null = undefined; - - constructor(draw: MapboxDraw) { - this.draw = draw; - } - - onAdd(map: Map) { - this.map = map; - this.container = document.createElement("div"); - this.container.className = "mapboxgl-ctrl-group mapboxgl-ctrl"; - this.root = createRoot(this.container); - this.root.render(); - this.container.onclick = () => { - this.draw.changeMode("draw_rectangle"); - }; - this.container.style.width = "29px"; - this.container.style.height = "29px"; - this.container.style.display = "flex"; - this.container.style.alignItems = "center"; - this.container.style.justifyContent = "center"; - - return this.container; - } - onRemove(_: Map) { - safeRemoveControl(this.container, this.root); - } -} - -export default DrawRectangleControl; diff --git a/src/components/map/mapbox/controls/ScaleControl.tsx b/src/components/map/mapbox/controls/ScaleControl.tsx index 9c0005d2..e50ca4f7 100644 --- a/src/components/map/mapbox/controls/ScaleControl.tsx +++ b/src/components/map/mapbox/controls/ScaleControl.tsx @@ -14,13 +14,13 @@ const ScaleControl = ({ unit = "metric", }: ScaleControlProps) => { const { map } = useContext(MapContext); - const [init, setInit] = useState(false); + const [_, setInit] = useState(false); useEffect(() => { if (!map) return; setInit((prev) => { - if (prev === false) { + if (!prev) { const scale = new MapboxScaleControl({ maxWidth: maxWidth, unit: unit, diff --git a/src/components/map/mapbox/controls/menu/MenuControl.tsx b/src/components/map/mapbox/controls/menu/MenuControl.tsx index 635d81df..6070715e 100644 --- a/src/components/map/mapbox/controls/menu/MenuControl.tsx +++ b/src/components/map/mapbox/controls/menu/MenuControl.tsx @@ -1,9 +1,9 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { cloneElement, useContext, useEffect, useState } from "react"; import { createRoot, Root } from "react-dom/client"; import MapContext from "../../MapContext"; import { Map as MapBox, IControl, MapMouseEvent } from "mapbox-gl"; import EventEmitter from "events"; -import { EVENT_MAP, EVENT_MENU, Menus } from "./Definition"; +import { ControlProps, EVENT_MAP, EVENT_MENU, Menus } from "./Definition"; const eventEmitter: EventEmitter = new EventEmitter(); @@ -18,15 +18,13 @@ class MapMenuControl implements IControl { private container: HTMLDivElement | null = null; private root: Root | null = null; private component: Menus; - private map: MapBox | null = null; // When the user clicks somewhere on the map, notify the MenuControl private mapClickHandler: (event: MapMouseEvent) => void; private mapMoveStartHandler: (event: MapMouseEvent) => void; - constructor(component: Menus, map: MapBox) { + constructor(component: Menus) { this.component = component; - this.map = map; // Handlers for map events this.mapClickHandler = (event: MapMouseEvent) => @@ -35,11 +33,6 @@ class MapMenuControl implements IControl { this.onClickHandler(event, undefined, EVENT_MAP.MOVE_START); } - updateComponent(component: Menus) { - this.component = component; - this.render(); - } - private render() { if (this.root && this.container) { this.root.render(this.component); @@ -94,7 +87,7 @@ const MenuControl: React.FC = ({ menu, }: MenuControlProps) => { const { map } = useContext(MapContext); - const [control, setControl] = useState(null); + const [_, setControl] = useState(null); // Creation effect useEffect(() => { @@ -102,7 +95,11 @@ const MenuControl: React.FC = ({ setControl((prev) => { if (!prev) { - const newControl = new MapMenuControl(menu, map); + // !!Must use cloneElement, to inject the map to the argument, so you + // can get it in the ControlProps + const newControl = new MapMenuControl( + cloneElement(menu, { map: map }) + ); map?.addControl(newControl, "top-right"); return newControl; } @@ -110,13 +107,6 @@ const MenuControl: React.FC = ({ }); }, [map, menu]); - // Props update effect - useEffect(() => { - if (control && menu) { - control.updateComponent(menu); - } - }, [control, menu]); - return ; }; diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index beceb2cd..aa41edb7 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -11,8 +11,7 @@ import { StaticLayersDef } from "../../../../components/map/mapbox/layers/Static import { MapboxWorldLayersDef } from "../../../../components/map/mapbox/layers/MapboxWorldLayer"; import ExpandableTextArea from "../../../../components/list/listItem/subitem/ExpandableTextArea"; import DetailSymbolLayer from "../../../../components/map/mapbox/layers/DetailSymbolLayer"; -import DrawControl from "../../../../components/map/mapbox/controls/DrawControl"; -import MapContext from "../../../../components/map/mapbox/MapContext"; +import DrawRectControl from "../../../../components/map/mapbox/controls/DrawRectControl"; import { MapboxEvent as MapEvent } from "mapbox-gl"; import BaseMapSwitcher from "../../../../components/map/mapbox/controls/menu/BaseMapSwitcher"; import MenuControl from "../../../../components/map/mapbox/controls/menu/MenuControl"; @@ -86,9 +85,6 @@ const AbstractAndDownloadPanel: FC = () => { }, [featureCollection]); const [minDateStamp, maxDateStamp] = getMinMaxDateStamps(); - - const { map } = useContext(MapContext); - const handleMapChange = useCallback( (event: MapEvent) => { // implement later @@ -148,8 +144,7 @@ const AbstractAndDownloadPanel: FC = () => { /> From 0a06a90b27268eb9da4d84c6732ccdbaf018c439 Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 09:41:02 +1100 Subject: [PATCH 02/11] Refactor and simply code, undo change which create problem --- src/components/map/mapbox/controls/menu/Definition.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/map/mapbox/controls/menu/Definition.tsx b/src/components/map/mapbox/controls/menu/Definition.tsx index 801595f6..1fa3643e 100644 --- a/src/components/map/mapbox/controls/menu/Definition.tsx +++ b/src/components/map/mapbox/controls/menu/Definition.tsx @@ -19,7 +19,9 @@ enum EVENT_BOOKMARK { EXPAND = "event-bookmark-expand", REMOVE_ALL = "event-bookmark-remove-all", } - +// For those who use MenuControl, please extend you props with this interface +// and the MenuControl will inject the map instance automatically for you +// you can define your onEvent if you want export interface ControlProps { map?: MapBox; onEvent?: (...args: any[]) => void; From b3e70cc7dc9dbe0d51d71ab5499a673460b36bb6 Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 11:45:03 +1100 Subject: [PATCH 03/11] Checkpoint refactor --- src/components/box/BBoxConditionBox.tsx | 9 +++-- src/components/box/DownloadConditionBox.tsx | 22 +++++-------- .../map/mapbox/controls/DrawRectControl.tsx | 4 +-- .../context/DownloadDefinitions.ts | 5 ++- .../context/detail-page-context.tsx | 6 ++-- .../context/detail-page-provider.tsx | 33 +++++++------------ .../subpages/side-cards/DownloadCard.tsx | 20 ++++++++--- .../tab-panels/AbstractAndDownloadPanel.tsx | 2 -- 8 files changed, 49 insertions(+), 52 deletions(-) diff --git a/src/components/box/BBoxConditionBox.tsx b/src/components/box/BBoxConditionBox.tsx index e7ea1858..78d7b752 100644 --- a/src/components/box/BBoxConditionBox.tsx +++ b/src/components/box/BBoxConditionBox.tsx @@ -9,14 +9,19 @@ import BBoxItem from "./BBoxItem"; interface BBoxConditionProps { bboxCondition: BBoxCondition; + onRemove?: () => void; } -const BBoxConditionBox: React.FC = ({ bboxCondition }) => { +const BBoxConditionBox: React.FC = ({ + onRemove, + bboxCondition, +}) => { const bbox = bboxCondition.bbox; return ( onRemove && onRemove()} > diff --git a/src/components/box/DownloadConditionBox.tsx b/src/components/box/DownloadConditionBox.tsx index 1857db17..970b7688 100644 --- a/src/components/box/DownloadConditionBox.tsx +++ b/src/components/box/DownloadConditionBox.tsx @@ -1,15 +1,15 @@ -import { DownloadConditionType } from "../../pages/detail-page/context/DownloadDefinitions"; -import React, { useCallback } from "react"; +import React from "react"; +import { + DownloadConditionType, + IDownloadCondition, +} from "../../pages/detail-page/context/DownloadDefinitions"; import { Grid, IconButton, Typography, useTheme } from "@mui/material"; import BBOX_IMG from "../../assets/icons/bbox.png"; import TIME_RANGE_IMG from "../../assets/images/time-range.png"; import CloseIcon from "@mui/icons-material/Close"; -import { useDetailPageContext } from "../../pages/detail-page/context/detail-page-context"; -interface DownloadConditionBoxProps { - type: DownloadConditionType; +interface DownloadConditionBoxProps extends IDownloadCondition { children: React.ReactNode; - conditionId: string; } const getIcon = (type: DownloadConditionType) => { @@ -41,15 +41,9 @@ const getTitle = (type: DownloadConditionType) => { const DownloadConditionBox: React.FC = ({ type, children, - conditionId, + removeCallback, }) => { const theme = useTheme(); - const { deleteDownloadConditionBy } = useDetailPageContext(); - - const onConditionRemove = useCallback(() => { - deleteDownloadConditionBy(conditionId); - }, [conditionId, deleteDownloadConditionBy]); - return ( = ({ - + diff --git a/src/components/map/mapbox/controls/DrawRectControl.tsx b/src/components/map/mapbox/controls/DrawRectControl.tsx index 978c80dc..05631007 100644 --- a/src/components/map/mapbox/controls/DrawRectControl.tsx +++ b/src/components/map/mapbox/controls/DrawRectControl.tsx @@ -27,13 +27,11 @@ interface DrawControlProps extends ControlProps { type: DownloadConditionType, conditions: IDownloadCondition[] ) => void; - draw: MapboxDraw; } const DrawRectControl: React.FC = ({ map, setDownloadConditions, - draw, }) => { const [open, setOpen] = useState(false); const mapDraw = useMemo( @@ -87,7 +85,7 @@ const DrawRectControl: React.FC = ({ const polygon = feature.geometry as Polygon; const bbox = turf.bbox(polygon); const id = _.toString(feature.id); - return new BBoxCondition(bbox, id); + return new BBoxCondition(id, bbox, () => mapDraw.delete(id)); }) || []; setDownloadConditions(DownloadConditionType.BBOX, bboxes); }; diff --git a/src/pages/detail-page/context/DownloadDefinitions.ts b/src/pages/detail-page/context/DownloadDefinitions.ts index 2c5063fa..b906ef6d 100644 --- a/src/pages/detail-page/context/DownloadDefinitions.ts +++ b/src/pages/detail-page/context/DownloadDefinitions.ts @@ -8,6 +8,7 @@ export enum DownloadConditionType { export interface IDownloadCondition { type: DownloadConditionType; id: string; + removeCallback?: () => void; } export class DateRangeCondition implements IDownloadCondition { @@ -27,10 +28,12 @@ export class BBoxCondition implements IDownloadCondition { type: DownloadConditionType; bbox: BBox; id: string; + removeCallback?: () => void; - constructor(bbox: BBox, id: string) { + constructor(id: string, bbox: BBox, removeCallback?: () => void) { this.type = DownloadConditionType.BBOX; this.id = id; this.bbox = bbox; + this.removeCallback = removeCallback; } } diff --git a/src/pages/detail-page/context/detail-page-context.tsx b/src/pages/detail-page/context/detail-page-context.tsx index 06e3c023..d670cc4a 100644 --- a/src/pages/detail-page/context/detail-page-context.tsx +++ b/src/pages/detail-page/context/detail-page-context.tsx @@ -20,8 +20,7 @@ interface DetailPageContextType { type: DownloadConditionType, conditions: IDownloadCondition[] ) => void; - deleteDownloadConditionBy: (id: string) => void; - mapDraw: MapboxDraw; + removeDownloadCondition: (condition: IDownloadCondition) => void; photos: SpatialExtentPhoto[]; setPhotos: Dispatch>; extentsPhotos: SpatialExtentPhoto[] | undefined; @@ -41,8 +40,7 @@ const DetailPageContextDefault = { isCollectionNotFound: false, downloadConditions: [], setDownloadConditions: () => {}, - deleteDownloadConditionBy: () => {}, - mapDraw: {} as MapboxDraw, + removeDownloadCondition: () => {}, photos: [] as SpatialExtentPhoto[], setPhotos: () => {}, extentsPhotos: [] as SpatialExtentPhoto[], diff --git a/src/pages/detail-page/context/detail-page-provider.tsx b/src/pages/detail-page/context/detail-page-provider.tsx index 37f28b3c..9b28d6d0 100644 --- a/src/pages/detail-page/context/detail-page-provider.tsx +++ b/src/pages/detail-page/context/detail-page-provider.tsx @@ -13,24 +13,10 @@ import { DownloadConditionType, IDownloadCondition, } from "./DownloadDefinitions"; -import MapboxDraw from "@mapbox/mapbox-gl-draw"; -import DrawRectangle from "../../../components/map/mapbox/controls/DrawRectangle"; import "mapbox-gl/dist/mapbox-gl.css"; import "@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css"; -const mapDraw = new MapboxDraw({ - displayControlsDefault: false, - controls: { - trash: true, - }, - defaultMode: "simple_select", - modes: { - ...MapboxDraw.modes, - draw_rectangle: DrawRectangle, - }, -}); - interface DetailPageProviderProps { children: ReactNode; } @@ -61,12 +47,16 @@ export const DetailPageProvider: FC = ({ }, [] ); - const deleteDownloadConditionBy = useCallback((id: string) => { - _setDownloadConditions((prev) => { - return prev.filter((condition) => condition.id !== id); - }); - mapDraw.delete(id); - }, []); + const removeDownloadCondition = useCallback( + (condition: IDownloadCondition) => { + _setDownloadConditions((prev) => + prev.filter( + (cs) => cs.type === condition.type && cs.id !== condition.id + ) + ); + }, + [] + ); const [photos, setPhotos] = useState([]); const [extentsPhotos, setExtentsPhotos] = useState([]); @@ -119,8 +109,7 @@ export const DetailPageProvider: FC = ({ isCollectionNotFound, downloadConditions, setDownloadConditions, - deleteDownloadConditionBy, - mapDraw, + removeDownloadCondition, photos, setPhotos, extentsPhotos, diff --git a/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx b/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx index f77b7dfb..66d9ec17 100644 --- a/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx +++ b/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { AccordionDetails, AccordionSummary, @@ -37,8 +37,8 @@ const options = [ const DownloadCard = () => { const theme = useTheme(); const [accordionExpanded, setAccordionExpanded] = useState(true); - const { isCollectionNotFound } = useDetailPageContext(); - const { downloadConditions } = useDetailPageContext(); + const { downloadConditions, isCollectionNotFound, removeDownloadCondition } = + useDetailPageContext(); const bboxConditions: BBoxCondition[] = useMemo(() => { const bboxConditions = downloadConditions.filter( @@ -65,6 +65,14 @@ const DownloadCard = () => { [theme] ); + const handleRemove = useCallback( + (c: BBoxCondition) => { + c.removeCallback && c.removeCallback(); + removeDownloadCondition(c); + }, + [removeDownloadCondition] + ); + return ( @@ -110,7 +118,11 @@ const DownloadCard = () => { {bboxConditions.map((bboxCondition, index) => { return ( - + handleRemove(bboxCondition)} + /> ); })} {dateRangeCondition.map((dateRangeCondition, index) => { diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index aa41edb7..3495abcc 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -32,7 +32,6 @@ const AbstractAndDownloadPanel: FC = () => { featureCollection, downloadConditions, setDownloadConditions, - mapDraw, } = useDetailPageContext(); const abstract = collection?.description ? collection.description : ""; @@ -146,7 +145,6 @@ const AbstractAndDownloadPanel: FC = () => { menu={ } /> From b914aa47e60473c212abd0b8d9a00100f2707805 Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 11:55:31 +1100 Subject: [PATCH 04/11] Refactor before DataRangeCondition change --- src/components/box/DateRangeConditionBox.tsx | 2 +- .../{DrawRectControl.tsx => menu/DrawRect.tsx} | 10 +++++----- .../map/mapbox/controls/{ => menu}/DrawRectangle.js | 0 .../subpages/tab-panels/AbstractAndDownloadPanel.tsx | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/components/map/mapbox/controls/{DrawRectControl.tsx => menu/DrawRect.tsx} (92%) rename src/components/map/mapbox/controls/{ => menu}/DrawRectangle.js (100%) diff --git a/src/components/box/DateRangeConditionBox.tsx b/src/components/box/DateRangeConditionBox.tsx index ccc9317e..642e06f0 100644 --- a/src/components/box/DateRangeConditionBox.tsx +++ b/src/components/box/DateRangeConditionBox.tsx @@ -21,7 +21,7 @@ const DateRangeConditionBox: React.FC = ({ return ( void; } -const DrawRectControl: React.FC = ({ +const DrawRect: React.FC = ({ map, setDownloadConditions, }) => { @@ -121,4 +121,4 @@ const DrawRectControl: React.FC = ({ ); }; -export default DrawRectControl; +export default DrawRect; diff --git a/src/components/map/mapbox/controls/DrawRectangle.js b/src/components/map/mapbox/controls/menu/DrawRectangle.js similarity index 100% rename from src/components/map/mapbox/controls/DrawRectangle.js rename to src/components/map/mapbox/controls/menu/DrawRectangle.js diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index 3495abcc..15aaf58e 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -1,4 +1,4 @@ -import React, { FC, useCallback, useContext, useMemo } from "react"; +import React, { FC, useCallback, useMemo } from "react"; import { Box, Grid, Stack } from "@mui/material"; import { padding } from "../../../../styles/constants"; import { useDetailPageContext } from "../../context/detail-page-context"; @@ -11,7 +11,7 @@ import { StaticLayersDef } from "../../../../components/map/mapbox/layers/Static import { MapboxWorldLayersDef } from "../../../../components/map/mapbox/layers/MapboxWorldLayer"; import ExpandableTextArea from "../../../../components/list/listItem/subitem/ExpandableTextArea"; import DetailSymbolLayer from "../../../../components/map/mapbox/layers/DetailSymbolLayer"; -import DrawRectControl from "../../../../components/map/mapbox/controls/DrawRectControl"; +import DrawRect from "../../../../components/map/mapbox/controls/menu/DrawRect"; import { MapboxEvent as MapEvent } from "mapbox-gl"; import BaseMapSwitcher from "../../../../components/map/mapbox/controls/menu/BaseMapSwitcher"; import MenuControl from "../../../../components/map/mapbox/controls/menu/MenuControl"; @@ -143,7 +143,7 @@ const AbstractAndDownloadPanel: FC = () => { /> } From 8b93517f043bdf7ba5f527c0cac4f532db94dbc5 Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 12:56:06 +1100 Subject: [PATCH 05/11] Simplify DataRange --- .../DateRangeControl/DateRangeControl.tsx | 95 ------------------- .../DateRangeControl/DateSliderControl.tsx | 69 -------------- .../DateSlider.tsx => menu/DateRange.tsx} | 82 +++++++++++++++- .../map/mapbox/controls/menu/Definition.tsx | 4 +- .../map/mapbox/controls/menu/MenuControl.tsx | 27 +++--- .../tab-panels/AbstractAndDownloadPanel.tsx | 14 ++- 6 files changed, 104 insertions(+), 187 deletions(-) delete mode 100644 src/components/map/mapbox/controls/DateRangeControl/DateRangeControl.tsx delete mode 100644 src/components/map/mapbox/controls/DateRangeControl/DateSliderControl.tsx rename src/components/map/mapbox/controls/{DateRangeControl/DateSlider.tsx => menu/DateRange.tsx} (56%) diff --git a/src/components/map/mapbox/controls/DateRangeControl/DateRangeControl.tsx b/src/components/map/mapbox/controls/DateRangeControl/DateRangeControl.tsx deleted file mode 100644 index c84cc749..00000000 --- a/src/components/map/mapbox/controls/DateRangeControl/DateRangeControl.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import mapboxgl, { IControl, Map } from "mapbox-gl"; -import { createRoot, Root } from "react-dom/client"; -import React, { useContext, useEffect, useMemo, useState } from "react"; -import MapContext from "../../MapContext"; -import DateSliderControlClass from "./DateSliderControl"; -import timeRange from "../../../../../../src/assets/images/time-range.png"; -import { - DownloadConditionType, - IDownloadCondition, -} from "../../../../../pages/detail-page/context/DownloadDefinitions"; -import { safeRemoveControl } from "../../../../../utils/MapUtils"; - -class DateRangeControlClass implements IControl { - private container: HTMLDivElement | null = null; - private iconRoot: Root | null = null; - private map: Map | undefined | null = undefined; - - private readonly setIsShowingDateSelector: () => void; - - constructor(setIsShowingDateSelector: () => void) { - this.setIsShowingDateSelector = setIsShowingDateSelector; - } - - onAdd(map: mapboxgl.Map): HTMLElement { - this.map = map; - this.container = document.createElement("div"); - this.container.className = "mapboxgl-ctrl-group mapboxgl-ctrl"; - this.iconRoot = createRoot(this.container); - this.iconRoot.render( - - ); - this.container.onclick = this.setIsShowingDateSelector; - return this.container; - } - - onRemove(_: Map) { - safeRemoveControl(this.container, this.iconRoot); - } -} - -interface DateRangeControlProps { - minDate: string; - maxDate: string; - setDownloadConditions: ( - type: DownloadConditionType, - conditions: IDownloadCondition[] - ) => void; -} - -const DateRangeControl: React.FC = ({ - minDate, - maxDate, - setDownloadConditions, -}) => { - const { map } = useContext(MapContext); - const [_, setDateRangeControl] = useState(null); - const dateSliderControl = useMemo( - () => new DateSliderControlClass(minDate, maxDate, setDownloadConditions), - [maxDate, minDate, setDownloadConditions] - ); - - const [isShowingSelector, setIsShowingSelector] = useState(false); - - const toggleIsShowingSelector = () => { - setIsShowingSelector((prev) => !prev); - }; - - useEffect(() => { - if (isShowingSelector) { - map?.addControl(dateSliderControl, "bottom-right"); - } else { - map?.removeControl(dateSliderControl); - } - }, [dateSliderControl, isShowingSelector, map]); - - useEffect(() => { - if (map === null) return; - setDateRangeControl((prev) => { - if (!prev) { - const dateRangeControl = new DateRangeControlClass( - toggleIsShowingSelector - ); - map?.addControl(dateRangeControl, "top-right"); - return dateRangeControl; - } - return prev; - }); - }, [map, setIsShowingSelector]); - - return <>; -}; - -export default DateRangeControl; diff --git a/src/components/map/mapbox/controls/DateRangeControl/DateSliderControl.tsx b/src/components/map/mapbox/controls/DateRangeControl/DateSliderControl.tsx deleted file mode 100644 index 134c2cf2..00000000 --- a/src/components/map/mapbox/controls/DateRangeControl/DateSliderControl.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from "react"; -import mapboxgl, { IControl, Map } from "mapbox-gl"; -import { createRoot, Root } from "react-dom/client"; -import DateSlider from "./DateSlider"; -import { - DownloadConditionType, - IDownloadCondition, - DateRangeCondition, -} from "../../../../../pages/detail-page/context/DownloadDefinitions"; -import dayjs from "dayjs"; -import { SIMPLE_DATE_FORMAT } from "../../../../../pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel"; -import { safeRemoveControl } from "../../../../../utils/MapUtils"; - -class DateSliderControlClass implements IControl { - private container: HTMLDivElement | null = null; - private readonly minDate: string; - private readonly maxDate: string; - private readonly setDownloadConditions: ( - type: DownloadConditionType, - conditions: IDownloadCondition[] - ) => void; - private readonly onDateRangeChange = (dateRangeStamps: number[]) => { - const start = dayjs(dateRangeStamps[0]).format(SIMPLE_DATE_FORMAT); - const end = dayjs(dateRangeStamps[1]).format(SIMPLE_DATE_FORMAT); - - if (this.minDate === start && this.maxDate === end) { - this.setDownloadConditions(DownloadConditionType.DATE_RANGE, []); - return; - } - - const dateRangeCondition = new DateRangeCondition(start, end, "date_range"); - this.setDownloadConditions(DownloadConditionType.DATE_RANGE, [ - dateRangeCondition, - ]); - }; - private root: Root | null = null; - - constructor( - minDate: string, - maxDate: string, - setDownloadConditions: ( - type: DownloadConditionType, - conditions: IDownloadCondition[] - ) => void - ) { - this.minDate = minDate; - this.maxDate = maxDate; - this.setDownloadConditions = setDownloadConditions; - } - - onAdd(_: mapboxgl.Map): HTMLElement { - this.container = document.createElement("div"); - this.container.className = "mapboxgl-ctrl mapboxgl-ctrl-group"; - this.root = createRoot(this.container!); - this.root.render( - - ); - return this.container; - } - - onRemove(_: Map) { - safeRemoveControl(this.container, this.root); - } -} -export default DateSliderControlClass; diff --git a/src/components/map/mapbox/controls/DateRangeControl/DateSlider.tsx b/src/components/map/mapbox/controls/menu/DateRange.tsx similarity index 56% rename from src/components/map/mapbox/controls/DateRangeControl/DateSlider.tsx rename to src/components/map/mapbox/controls/menu/DateRange.tsx index fb639cbc..d4f2dbf4 100644 --- a/src/components/map/mapbox/controls/DateRangeControl/DateSlider.tsx +++ b/src/components/map/mapbox/controls/menu/DateRange.tsx @@ -1,10 +1,18 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; -import { Grid, Typography } from "@mui/material"; -import PlainSlider from "../../../../common/slider/PlainSlider"; -import { dateToValue, valueToDate } from "../../../../../utils/DateUtils"; +import timeRange from "../../../../../assets/images/time-range.png"; +import { + DateRangeCondition, + DownloadConditionType, + IDownloadCondition, +} from "../../../../../pages/detail-page/context/DownloadDefinitions"; +import { ControlProps } from "./Definition"; +import { Grid, IconButton, Typography } from "@mui/material"; +import { MapControl } from "./MenuControl"; import dayjs from "dayjs"; -import _ from "lodash"; import { SIMPLE_DATE_FORMAT } from "../../../../../pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel"; +import { dateToValue, valueToDate } from "../../../../../utils/DateUtils"; +import _ from "lodash"; +import PlainSlider from "../../../../common/slider/PlainSlider"; interface DateSliderProps { minDate: string; @@ -12,6 +20,15 @@ interface DateSliderProps { onDateRangeChange: (dateRangeStamp: number[]) => void; } +interface DateRangeControlProps extends ControlProps { + minDate: string; + maxDate: string; + setDownloadConditions: ( + type: DownloadConditionType, + conditions: IDownloadCondition[] + ) => void; +} + const DateSlider: React.FC = ({ minDate, maxDate, @@ -86,4 +103,59 @@ const DateSlider: React.FC = ({ ); }; -export default DateSlider; +const DateRange: React.FC = ({ + minDate, + maxDate, + setDownloadConditions, + map, +}) => { + const [isShowingSelector, setIsShowingSelector] = useState(false); + + const onDateRangeChange = useCallback( + (dateRangeStamps: number[]) => { + const start = dayjs(dateRangeStamps[0]).format(SIMPLE_DATE_FORMAT); + const end = dayjs(dateRangeStamps[1]).format(SIMPLE_DATE_FORMAT); + + if (minDate === start && maxDate === end) { + setDownloadConditions(DownloadConditionType.DATE_RANGE, []); + return; + } + + const dateRangeCondition = new DateRangeCondition( + start, + end, + "date_range" + ); + setDownloadConditions(DownloadConditionType.DATE_RANGE, [ + dateRangeCondition, + ]); + }, + [maxDate, minDate, setDownloadConditions] + ); + + useEffect(() => { + if (isShowingSelector) { + const slider: MapControl = new MapControl( + ( + + ) + ); + map?.addControl(slider, "bottom-right"); + return () => { + map?.removeControl(slider); + }; + } + }, [isShowingSelector, map, maxDate, minDate, onDateRangeChange]); + + return ( + setIsShowingSelector((prev) => !prev)}> + + + ); +}; + +export default DateRange; diff --git a/src/components/map/mapbox/controls/menu/Definition.tsx b/src/components/map/mapbox/controls/menu/Definition.tsx index 1fa3643e..aac068c8 100644 --- a/src/components/map/mapbox/controls/menu/Definition.tsx +++ b/src/components/map/mapbox/controls/menu/Definition.tsx @@ -27,14 +27,14 @@ export interface ControlProps { onEvent?: (...args: any[]) => void; } -export type Menus = React.ReactElement< +export type MapControlType = React.ReactElement< ControlProps, string | React.JSXElementConstructor >; export interface MenuClickedEvent { event: MouseEvent; - component: Menus; + component: MapControlType; } export interface BookmarkEvent { diff --git a/src/components/map/mapbox/controls/menu/MenuControl.tsx b/src/components/map/mapbox/controls/menu/MenuControl.tsx index 6070715e..7d2798a6 100644 --- a/src/components/map/mapbox/controls/menu/MenuControl.tsx +++ b/src/components/map/mapbox/controls/menu/MenuControl.tsx @@ -3,7 +3,12 @@ import { createRoot, Root } from "react-dom/client"; import MapContext from "../../MapContext"; import { Map as MapBox, IControl, MapMouseEvent } from "mapbox-gl"; import EventEmitter from "events"; -import { ControlProps, EVENT_MAP, EVENT_MENU, Menus } from "./Definition"; +import { + ControlProps, + EVENT_MAP, + EVENT_MENU, + MapControlType, +} from "./Definition"; const eventEmitter: EventEmitter = new EventEmitter(); @@ -11,19 +16,19 @@ const leftPadding = "15px"; const rightPadding = "15px"; interface MenuControlProps { - menu: Menus | null; + menu: MapControlType | null; } -class MapMenuControl implements IControl { +class MapControl implements IControl { private container: HTMLDivElement | null = null; private root: Root | null = null; - private component: Menus; + private readonly component: MapControlType; // When the user clicks somewhere on the map, notify the MenuControl - private mapClickHandler: (event: MapMouseEvent) => void; - private mapMoveStartHandler: (event: MapMouseEvent) => void; + private readonly mapClickHandler: (event: MapMouseEvent) => void; + private readonly mapMoveStartHandler: (event: MapMouseEvent) => void; - constructor(component: Menus) { + constructor(component: MapControlType) { this.component = component; // Handlers for map events @@ -73,7 +78,7 @@ class MapMenuControl implements IControl { onClickHandler( event: MouseEvent | MapMouseEvent, - component: Menus | undefined, + component: MapControlType | undefined, type: string = EVENT_MENU.CLICKED ) { eventEmitter.emit(type, { @@ -87,7 +92,7 @@ const MenuControl: React.FC = ({ menu, }: MenuControlProps) => { const { map } = useContext(MapContext); - const [_, setControl] = useState(null); + const [_, setControl] = useState(null); // Creation effect useEffect(() => { @@ -97,7 +102,7 @@ const MenuControl: React.FC = ({ if (!prev) { // !!Must use cloneElement, to inject the map to the argument, so you // can get it in the ControlProps - const newControl = new MapMenuControl( + const newControl = new MapControl( cloneElement(menu, { map: map }) ); map?.addControl(newControl, "top-right"); @@ -112,4 +117,4 @@ const MenuControl: React.FC = ({ export default MenuControl; -export { eventEmitter, leftPadding, rightPadding }; +export { eventEmitter, leftPadding, rightPadding, MapControl }; diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index 15aaf58e..64874425 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -15,7 +15,7 @@ import DrawRect from "../../../../components/map/mapbox/controls/menu/DrawRect"; import { MapboxEvent as MapEvent } from "mapbox-gl"; import BaseMapSwitcher from "../../../../components/map/mapbox/controls/menu/BaseMapSwitcher"; import MenuControl from "../../../../components/map/mapbox/controls/menu/MenuControl"; -import DateRangeControl from "../../../../components/map/mapbox/controls/DateRangeControl/DateRangeControl"; +import DateRange from "../../../../components/map/mapbox/controls/menu/DateRange"; import dayjs from "dayjs"; import { DateRangeCondition, @@ -148,10 +148,14 @@ const AbstractAndDownloadPanel: FC = () => { /> } /> - + } /> From 1f240fda5b651c276166c93398d79c433df3d00b Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 15:07:49 +1100 Subject: [PATCH 06/11] Checkpoint data range refactor --- src/components/box/DateRangeConditionBox.tsx | 5 ++++- src/components/box/DownloadConditionBox.tsx | 5 ++++- src/pages/detail-page/context/DownloadDefinitions.ts | 7 ++++++- src/pages/detail-page/context/detail-page-provider.tsx | 2 +- .../detail-page/subpages/side-cards/DownloadCard.tsx | 9 ++++++--- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/box/DateRangeConditionBox.tsx b/src/components/box/DateRangeConditionBox.tsx index 642e06f0..88252b6a 100644 --- a/src/components/box/DateRangeConditionBox.tsx +++ b/src/components/box/DateRangeConditionBox.tsx @@ -8,9 +8,11 @@ import { Typography } from "@mui/material"; interface DateRangeConditionBoxProps { dateRangeCondition: DateRangeCondition; + onRemove?: () => void; } const DateRangeConditionBox: React.FC = ({ + onRemove, dateRangeCondition, }) => { const start = useMemo( @@ -20,8 +22,9 @@ const DateRangeConditionBox: React.FC = ({ const end = useMemo(() => dateRangeCondition.end, [dateRangeCondition.end]); return ( onRemove && onRemove()} > void; } @@ -24,7 +27,9 @@ export class DateRangeCondition implements IDownloadCondition { } } -export class BBoxCondition implements IDownloadCondition { +export class BBoxCondition + implements IDownloadCondition, IDownloadConditionCallback +{ type: DownloadConditionType; bbox: BBox; id: string; diff --git a/src/pages/detail-page/context/detail-page-provider.tsx b/src/pages/detail-page/context/detail-page-provider.tsx index 9b28d6d0..979a8d5b 100644 --- a/src/pages/detail-page/context/detail-page-provider.tsx +++ b/src/pages/detail-page/context/detail-page-provider.tsx @@ -51,7 +51,7 @@ export const DetailPageProvider: FC = ({ (condition: IDownloadCondition) => { _setDownloadConditions((prev) => prev.filter( - (cs) => cs.type === condition.type && cs.id !== condition.id + (cs) => !(cs.type === condition.type && cs.id === condition.id) ) ); }, diff --git a/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx b/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx index 66d9ec17..032ad430 100644 --- a/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx +++ b/src/pages/detail-page/subpages/side-cards/DownloadCard.tsx @@ -26,6 +26,8 @@ import { BBoxCondition, DownloadConditionType, DateRangeCondition, + IDownloadCondition, + IDownloadConditionCallback, } from "../../context/DownloadDefinitions"; import DateRangeConditionBox from "../../../../components/box/DateRangeConditionBox"; @@ -48,10 +50,10 @@ const DownloadCard = () => { }, [downloadConditions]); const dateRangeCondition: DateRangeCondition[] = useMemo(() => { - const timerangeConditions = downloadConditions.filter( + const timeRangeConditions = downloadConditions.filter( (condition) => condition.type === DownloadConditionType.DATE_RANGE ); - return timerangeConditions as DateRangeCondition[]; + return timeRangeConditions as DateRangeCondition[]; }, [downloadConditions]); const selectSxProps = useMemo( @@ -66,7 +68,7 @@ const DownloadCard = () => { ); const handleRemove = useCallback( - (c: BBoxCondition) => { + (c: IDownloadConditionCallback & IDownloadCondition) => { c.removeCallback && c.removeCallback(); removeDownloadCondition(c); }, @@ -130,6 +132,7 @@ const DownloadCard = () => { handleRemove(dateRangeCondition)} /> ); })} From 100da28aec1718bac3d06c96b736cc8ce93aaa2f Mon Sep 17 00:00:00 2001 From: rng Date: Fri, 20 Dec 2024 17:26:54 +1100 Subject: [PATCH 07/11] Checkpoint data range refactor const --- src/components/common/constants.ts | 1 + .../map/mapbox/controls/menu/DateRange.tsx | 20 +++--- .../context/DownloadDefinitions.ts | 7 +- .../tab-panels/AbstractAndDownloadPanel.tsx | 67 +++++++++++-------- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/src/components/common/constants.ts b/src/components/common/constants.ts index 7b98cb22..78f2f327 100644 --- a/src/components/common/constants.ts +++ b/src/components/common/constants.ts @@ -1,6 +1,7 @@ const dateDefault = { // Must use this format to do search, we do not care about the time DATE_TIME_FORMAT: "YYYY-MM-DDT00:00:00[Z]", + SIMPLE_DATE_FORMAT: "MM-YYYY", min: new Date("01/01/1970"), max: new Date(), }; diff --git a/src/components/map/mapbox/controls/menu/DateRange.tsx b/src/components/map/mapbox/controls/menu/DateRange.tsx index d4f2dbf4..147cd823 100644 --- a/src/components/map/mapbox/controls/menu/DateRange.tsx +++ b/src/components/map/mapbox/controls/menu/DateRange.tsx @@ -9,10 +9,10 @@ import { ControlProps } from "./Definition"; import { Grid, IconButton, Typography } from "@mui/material"; import { MapControl } from "./MenuControl"; import dayjs from "dayjs"; -import { SIMPLE_DATE_FORMAT } from "../../../../../pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel"; import { dateToValue, valueToDate } from "../../../../../utils/DateUtils"; import _ from "lodash"; import PlainSlider from "../../../../common/slider/PlainSlider"; +import { dateDefault } from "../../../../common/constants"; interface DateSliderProps { minDate: string; @@ -35,8 +35,8 @@ const DateSlider: React.FC = ({ onDateRangeChange, }) => { const [dateRangeStamp, setDateRangeStamp] = useState([ - dateToValue(dayjs(minDate, SIMPLE_DATE_FORMAT)), - dateToValue(dayjs(maxDate, SIMPLE_DATE_FORMAT)), + dateToValue(dayjs(minDate, dateDefault.SIMPLE_DATE_FORMAT)), + dateToValue(dayjs(maxDate, dateDefault.SIMPLE_DATE_FORMAT)), ]); const parentRef = useRef(null); @@ -80,12 +80,12 @@ const DateSlider: React.FC = ({ - valueToDate(value).format(SIMPLE_DATE_FORMAT) + valueToDate(value).format(dateDefault.SIMPLE_DATE_FORMAT) } /> @@ -113,8 +113,12 @@ const DateRange: React.FC = ({ const onDateRangeChange = useCallback( (dateRangeStamps: number[]) => { - const start = dayjs(dateRangeStamps[0]).format(SIMPLE_DATE_FORMAT); - const end = dayjs(dateRangeStamps[1]).format(SIMPLE_DATE_FORMAT); + const start = dayjs(dateRangeStamps[0]).format( + dateDefault.SIMPLE_DATE_FORMAT + ); + const end = dayjs(dateRangeStamps[1]).format( + dateDefault.SIMPLE_DATE_FORMAT + ); if (minDate === start && maxDate === end) { setDownloadConditions(DownloadConditionType.DATE_RANGE, []); diff --git a/src/pages/detail-page/context/DownloadDefinitions.ts b/src/pages/detail-page/context/DownloadDefinitions.ts index c6c0b8f6..88d404dc 100644 --- a/src/pages/detail-page/context/DownloadDefinitions.ts +++ b/src/pages/detail-page/context/DownloadDefinitions.ts @@ -1,4 +1,6 @@ import { BBox } from "geojson"; +import dayjs from "dayjs"; +import { dateDefault } from "../../../components/common/constants"; export enum DownloadConditionType { BBOX = "bbox", @@ -14,11 +16,14 @@ export interface IDownloadConditionCallback { removeCallback?: () => void; } -export class DateRangeCondition implements IDownloadCondition { +export class DateRangeCondition + implements IDownloadCondition, IDownloadConditionCallback +{ type: DownloadConditionType = DownloadConditionType.DATE_RANGE; id: string; start: string; end: string; + removeCallback?: () => void; constructor(start: string, end: string, id: string) { this.id = id; diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index 64874425..3f791d94 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -21,10 +21,31 @@ import { DateRangeCondition, DownloadConditionType, } from "../../context/DownloadDefinitions"; +import { dateDefault } from "../../../../components/common/constants"; +import { FeatureCollection, GeoJsonProperties, Point } from "geojson"; const TRUNCATE_COUNT = 800; -export const SIMPLE_DATE_FORMAT = "MM-YYYY"; +const getMinMaxDateStamps = ( + featureCollection: FeatureCollection | undefined +) => { + let minDate = dayjs(dateDefault.min); + let maxDate = dayjs(dateDefault.max); + + if (featureCollection) { + featureCollection.features?.forEach((feature) => { + const start = dayjs(feature.properties?.startTime); + const end = dayjs(feature.properties?.endTime); + if (start.isBefore(minDate)) { + minDate = start; + } + if (end.isAfter(maxDate)) { + maxDate = end; + } + }); + } + return [minDate, maxDate]; +}; const AbstractAndDownloadPanel: FC = () => { const { @@ -34,6 +55,7 @@ const AbstractAndDownloadPanel: FC = () => { setDownloadConditions, } = useDetailPageContext(); + const [minDateStamp, maxDateStamp] = getMinMaxDateStamps(featureCollection); const abstract = collection?.description ? collection.description : ""; const mapContainerId = "map-detail-container-id"; @@ -49,8 +71,14 @@ const AbstractAndDownloadPanel: FC = () => { return featureCollection; } const dateRangeCondition = dateRangeConditionGeneric as DateRangeCondition; - const conditionStart = dayjs(dateRangeCondition.start, SIMPLE_DATE_FORMAT); - const conditionEnd = dayjs(dateRangeCondition.end, SIMPLE_DATE_FORMAT); + const conditionStart = dayjs( + dateRangeCondition.start, + dateDefault.SIMPLE_DATE_FORMAT + ); + const conditionEnd = dayjs( + dateRangeCondition.end, + dateDefault.SIMPLE_DATE_FORMAT + ); const filteredFeatures = featureCollection.features?.filter((feature) => { const start = dayjs(feature.properties?.startTime, "YYYY-MM"); @@ -64,26 +92,6 @@ const AbstractAndDownloadPanel: FC = () => { }; }, [downloadConditions, featureCollection]); - const getMinMaxDateStamps = useCallback(() => { - let minDate = dayjs(); - let maxDate = dayjs("10-1800", SIMPLE_DATE_FORMAT); - - if (featureCollection) { - featureCollection.features?.forEach((feature) => { - const start = dayjs(feature.properties?.startTime); - const end = dayjs(feature.properties?.endTime); - if (start.isBefore(minDate)) { - minDate = start; - } - if (end.isAfter(maxDate)) { - maxDate = end; - } - }); - } - return [minDate, maxDate]; - }, [featureCollection]); - - const [minDateStamp, maxDateStamp] = getMinMaxDateStamps(); const handleMapChange = useCallback( (event: MapEvent) => { // implement later @@ -92,9 +100,8 @@ const AbstractAndDownloadPanel: FC = () => { [] ); - if (!collection) return; return ( - <> + collection && ( @@ -151,8 +158,12 @@ const AbstractAndDownloadPanel: FC = () => { } @@ -169,7 +180,7 @@ const AbstractAndDownloadPanel: FC = () => { - + ) ); }; From 0897c4b20849b7c4f36865bf02d2dbe0ca0e576d Mon Sep 17 00:00:00 2001 From: rng Date: Mon, 23 Dec 2024 10:20:49 +1100 Subject: [PATCH 08/11] Checkpoint data range refactor remove debounce --- .../map/mapbox/controls/menu/DateRange.tsx | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/src/components/map/mapbox/controls/menu/DateRange.tsx b/src/components/map/mapbox/controls/menu/DateRange.tsx index 147cd823..c884daa6 100644 --- a/src/components/map/mapbox/controls/menu/DateRange.tsx +++ b/src/components/map/mapbox/controls/menu/DateRange.tsx @@ -17,7 +17,10 @@ import { dateDefault } from "../../../../common/constants"; interface DateSliderProps { minDate: string; maxDate: string; - onDateRangeChange: (dateRangeStamp: number[]) => void; + onDateRangeChange: ( + event: Event | React.SyntheticEvent, + value: number | number[] + ) => void; } interface DateRangeControlProps extends ControlProps { @@ -38,36 +41,17 @@ const DateSlider: React.FC = ({ dateToValue(dayjs(minDate, dateDefault.SIMPLE_DATE_FORMAT)), dateToValue(dayjs(maxDate, dateDefault.SIMPLE_DATE_FORMAT)), ]); - const parentRef = useRef(null); - - const debounceSliderChange = useRef<_.DebouncedFunc< - (dateStamps: any) => void - > | null>(null); - - useEffect(() => { - debounceSliderChange.current = _.debounce((_: number | number[]) => { - onDateRangeChange(dateRangeStamp); - }, 500); - - return () => { - debounceSliderChange.current?.cancel(); - }; - }, [dateRangeStamp, onDateRangeChange]); - - useEffect(() => { - debounceSliderChange?.current?.(dateRangeStamp); - }, [dateRangeStamp]); - const handleSliderChange = useCallback( (_: Event, newValue: number | number[]) => { - setDateRangeStamp(newValue as number[]); + const v = newValue as number[]; + setDateRangeStamp(v); }, [] ); return ( - + {minDate} @@ -82,6 +66,7 @@ const DateSlider: React.FC = ({ value={dateRangeStamp} min={dateToValue(dayjs(minDate, dateDefault.SIMPLE_DATE_FORMAT))} max={dateToValue(dayjs(maxDate, dateDefault.SIMPLE_DATE_FORMAT))} + onChangeCommitted={onDateRangeChange} onChange={handleSliderChange} valueLabelDisplay="auto" valueLabelFormat={(value: number) => @@ -112,27 +97,26 @@ const DateRange: React.FC = ({ const [isShowingSelector, setIsShowingSelector] = useState(false); const onDateRangeChange = useCallback( - (dateRangeStamps: number[]) => { - const start = dayjs(dateRangeStamps[0]).format( - dateDefault.SIMPLE_DATE_FORMAT - ); - const end = dayjs(dateRangeStamps[1]).format( - dateDefault.SIMPLE_DATE_FORMAT - ); + ( + _: Event | React.SyntheticEvent, + dateRangeStamps: number | number[] + ) => { + const d = dateRangeStamps as number[]; + const start = dayjs(d[0]).format(dateDefault.SIMPLE_DATE_FORMAT); + const end = dayjs(d[1]).format(dateDefault.SIMPLE_DATE_FORMAT); if (minDate === start && maxDate === end) { setDownloadConditions(DownloadConditionType.DATE_RANGE, []); - return; + } else { + const dateRangeCondition = new DateRangeCondition( + start, + end, + "date_range" + ); + setDownloadConditions(DownloadConditionType.DATE_RANGE, [ + dateRangeCondition, + ]); } - - const dateRangeCondition = new DateRangeCondition( - start, - end, - "date_range" - ); - setDownloadConditions(DownloadConditionType.DATE_RANGE, [ - dateRangeCondition, - ]); }, [maxDate, minDate, setDownloadConditions] ); From e1e06e99994ef8a25e87ec0c9168f5e7b1488348 Mon Sep 17 00:00:00 2001 From: rng Date: Mon, 23 Dec 2024 11:05:33 +1100 Subject: [PATCH 09/11] Checkpoint data range before fix show hide --- .../map/mapbox/controls/menu/DateRange.tsx | 24 ++++++++++++------- .../map/mapbox/controls/menu/DrawRect.tsx | 10 ++++---- .../context/DownloadDefinitions.ts | 10 +++++--- .../context/detail-page-context.tsx | 6 ++--- .../context/detail-page-provider.tsx | 12 +++++++--- .../tab-panels/AbstractAndDownloadPanel.tsx | 10 +++++--- 6 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/components/map/mapbox/controls/menu/DateRange.tsx b/src/components/map/mapbox/controls/menu/DateRange.tsx index c884daa6..cd3e58fb 100644 --- a/src/components/map/mapbox/controls/menu/DateRange.tsx +++ b/src/components/map/mapbox/controls/menu/DateRange.tsx @@ -4,13 +4,13 @@ import { DateRangeCondition, DownloadConditionType, IDownloadCondition, + IDownloadConditionCallback, } from "../../../../../pages/detail-page/context/DownloadDefinitions"; import { ControlProps } from "./Definition"; import { Grid, IconButton, Typography } from "@mui/material"; import { MapControl } from "./MenuControl"; import dayjs from "dayjs"; import { dateToValue, valueToDate } from "../../../../../utils/DateUtils"; -import _ from "lodash"; import PlainSlider from "../../../../common/slider/PlainSlider"; import { dateDefault } from "../../../../common/constants"; @@ -26,10 +26,10 @@ interface DateSliderProps { interface DateRangeControlProps extends ControlProps { minDate: string; maxDate: string; - setDownloadConditions: ( + getAndSetDownloadConditions: ( type: DownloadConditionType, conditions: IDownloadCondition[] - ) => void; + ) => IDownloadCondition[]; } const DateSlider: React.FC = ({ @@ -91,7 +91,7 @@ const DateSlider: React.FC = ({ const DateRange: React.FC = ({ minDate, maxDate, - setDownloadConditions, + getAndSetDownloadConditions, map, }) => { const [isShowingSelector, setIsShowingSelector] = useState(false); @@ -106,19 +106,27 @@ const DateRange: React.FC = ({ const end = dayjs(d[1]).format(dateDefault.SIMPLE_DATE_FORMAT); if (minDate === start && maxDate === end) { - setDownloadConditions(DownloadConditionType.DATE_RANGE, []); + const prev = getAndSetDownloadConditions( + DownloadConditionType.DATE_RANGE, + [] + ); + prev.forEach((p) => { + const callback = (p as IDownloadConditionCallback).removeCallback; + callback && callback(); + }); } else { const dateRangeCondition = new DateRangeCondition( + "date_range", start, end, - "date_range" + () => setIsShowingSelector(false) ); - setDownloadConditions(DownloadConditionType.DATE_RANGE, [ + getAndSetDownloadConditions(DownloadConditionType.DATE_RANGE, [ dateRangeCondition, ]); } }, - [maxDate, minDate, setDownloadConditions] + [maxDate, minDate, getAndSetDownloadConditions] ); useEffect(() => { diff --git a/src/components/map/mapbox/controls/menu/DrawRect.tsx b/src/components/map/mapbox/controls/menu/DrawRect.tsx index f0637254..36b830bd 100644 --- a/src/components/map/mapbox/controls/menu/DrawRect.tsx +++ b/src/components/map/mapbox/controls/menu/DrawRect.tsx @@ -23,15 +23,15 @@ import DrawRectangle from "./DrawRectangle"; import { ControlProps } from "./Definition"; interface DrawControlProps extends ControlProps { - setDownloadConditions: ( + getAndSetDownloadConditions: ( type: DownloadConditionType, conditions: IDownloadCondition[] - ) => void; + ) => IDownloadCondition[]; } const DrawRect: React.FC = ({ map, - setDownloadConditions, + getAndSetDownloadConditions, }) => { const [open, setOpen] = useState(false); const mapDraw = useMemo( @@ -87,7 +87,7 @@ const DrawRect: React.FC = ({ const id = _.toString(feature.id); return new BBoxCondition(id, bbox, () => mapDraw.delete(id)); }) || []; - setDownloadConditions(DownloadConditionType.BBOX, bboxes); + getAndSetDownloadConditions(DownloadConditionType.BBOX, bboxes); }; map.addControl(mapDraw); @@ -106,7 +106,7 @@ const DrawRect: React.FC = ({ } }; } - }, [mapDraw, map, setDownloadConditions]); + }, [mapDraw, map, getAndSetDownloadConditions]); return ( void; - constructor(start: string, end: string, id: string) { + constructor( + id: string, + start: string, + end: string, + removeCallback?: () => void + ) { this.id = id; this.start = start; this.end = end; + this.removeCallback = removeCallback; } } diff --git a/src/pages/detail-page/context/detail-page-context.tsx b/src/pages/detail-page/context/detail-page-context.tsx index d670cc4a..10d01bcf 100644 --- a/src/pages/detail-page/context/detail-page-context.tsx +++ b/src/pages/detail-page/context/detail-page-context.tsx @@ -16,10 +16,10 @@ interface DetailPageContextType { featureCollection: FeatureCollection | undefined; isCollectionNotFound: boolean; downloadConditions: IDownloadCondition[]; - setDownloadConditions: ( + getAndSetDownloadConditions: ( type: DownloadConditionType, conditions: IDownloadCondition[] - ) => void; + ) => IDownloadCondition[]; removeDownloadCondition: (condition: IDownloadCondition) => void; photos: SpatialExtentPhoto[]; setPhotos: Dispatch>; @@ -39,7 +39,7 @@ const DetailPageContextDefault = { featureCollection: {} as FeatureCollection | undefined, isCollectionNotFound: false, downloadConditions: [], - setDownloadConditions: () => {}, + getAndSetDownloadConditions: () => [], removeDownloadCondition: () => {}, photos: [] as SpatialExtentPhoto[], setPhotos: () => {}, diff --git a/src/pages/detail-page/context/detail-page-provider.tsx b/src/pages/detail-page/context/detail-page-provider.tsx index 979a8d5b..802f1937 100644 --- a/src/pages/detail-page/context/detail-page-provider.tsx +++ b/src/pages/detail-page/context/detail-page-provider.tsx @@ -37,13 +37,19 @@ export const DetailPageProvider: FC = ({ const [downloadConditions, _setDownloadConditions] = useState< IDownloadCondition[] >([]); - const setDownloadConditions = useCallback( - (type: DownloadConditionType, conditions: IDownloadCondition[]) => { + const getAndSetDownloadConditions = useCallback( + ( + type: DownloadConditionType, + conditions: IDownloadCondition[] + ): IDownloadCondition[] => { + let p: IDownloadCondition[] = []; _setDownloadConditions((prev) => { + p = prev; return prev .filter((condition) => condition.type !== type) .concat(conditions); }); + return p; }, [] ); @@ -108,7 +114,7 @@ export const DetailPageProvider: FC = ({ featureCollection: features, isCollectionNotFound, downloadConditions, - setDownloadConditions, + getAndSetDownloadConditions, removeDownloadCondition, photos, setPhotos, diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index 3f791d94..6d546830 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -52,7 +52,7 @@ const AbstractAndDownloadPanel: FC = () => { collection, featureCollection, downloadConditions, - setDownloadConditions, + getAndSetDownloadConditions, } = useDetailPageContext(); const [minDateStamp, maxDateStamp] = getMinMaxDateStamps(featureCollection); @@ -151,7 +151,9 @@ const AbstractAndDownloadPanel: FC = () => { } /> @@ -164,7 +166,9 @@ const AbstractAndDownloadPanel: FC = () => { maxDate={maxDateStamp.format( dateDefault.SIMPLE_DATE_FORMAT )} - setDownloadConditions={setDownloadConditions} + getAndSetDownloadConditions={ + getAndSetDownloadConditions + } /> } /> From 6b5665ef8495a66c449d12b05ec4379dd8a939fa Mon Sep 17 00:00:00 2001 From: rng Date: Mon, 23 Dec 2024 11:38:39 +1100 Subject: [PATCH 10/11] Simplified date range --- .../map/mapbox/controls/menu/DateRange.tsx | 47 ++++++++++++++++--- .../tab-panels/AbstractAndDownloadPanel.tsx | 4 +- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/components/map/mapbox/controls/menu/DateRange.tsx b/src/components/map/mapbox/controls/menu/DateRange.tsx index cd3e58fb..1003eb71 100644 --- a/src/components/map/mapbox/controls/menu/DateRange.tsx +++ b/src/components/map/mapbox/controls/menu/DateRange.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import timeRange from "../../../../../assets/images/time-range.png"; import { DateRangeCondition, @@ -15,6 +15,8 @@ import PlainSlider from "../../../../common/slider/PlainSlider"; import { dateDefault } from "../../../../common/constants"; interface DateSliderProps { + currentMinDate: string | undefined; + currentMaxDate: string | undefined; minDate: string; maxDate: string; onDateRangeChange: ( @@ -33,13 +35,25 @@ interface DateRangeControlProps extends ControlProps { } const DateSlider: React.FC = ({ + currentMinDate, + currentMaxDate, minDate, maxDate, onDateRangeChange, }) => { const [dateRangeStamp, setDateRangeStamp] = useState([ - dateToValue(dayjs(minDate, dateDefault.SIMPLE_DATE_FORMAT)), - dateToValue(dayjs(maxDate, dateDefault.SIMPLE_DATE_FORMAT)), + dateToValue( + dayjs( + currentMinDate ? currentMinDate : minDate, + dateDefault.SIMPLE_DATE_FORMAT + ) + ), + dateToValue( + dayjs( + currentMaxDate ? currentMaxDate : maxDate, + dateDefault.SIMPLE_DATE_FORMAT + ) + ), ]); const handleSliderChange = useCallback( (_: Event, newValue: number | number[]) => { @@ -94,7 +108,13 @@ const DateRange: React.FC = ({ getAndSetDownloadConditions, map, }) => { - const [isShowingSelector, setIsShowingSelector] = useState(false); + const [isShowingSelector, setIsShowingSelector] = useState(false); + const [currentMinDate, setCurrentMinDate] = useState( + undefined + ); + const [currentMaxDate, setCurrentMaxDate] = useState( + undefined + ); const onDateRangeChange = useCallback( ( @@ -119,11 +139,16 @@ const DateRange: React.FC = ({ "date_range", start, end, - () => setIsShowingSelector(false) + () => { + setCurrentMinDate(undefined); + setCurrentMaxDate(undefined); + } ); getAndSetDownloadConditions(DownloadConditionType.DATE_RANGE, [ dateRangeCondition, ]); + setCurrentMinDate(start); + setCurrentMaxDate(end); } }, [maxDate, minDate, getAndSetDownloadConditions] @@ -134,6 +159,8 @@ const DateRange: React.FC = ({ const slider: MapControl = new MapControl( ( = ({ map?.removeControl(slider); }; } - }, [isShowingSelector, map, maxDate, minDate, onDateRangeChange]); + }, [ + currentMaxDate, + currentMinDate, + isShowingSelector, + map, + maxDate, + minDate, + onDateRangeChange, + ]); return ( setIsShowingSelector((prev) => !prev)}> diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index 6d546830..ce41ce36 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -22,7 +22,7 @@ import { DownloadConditionType, } from "../../context/DownloadDefinitions"; import { dateDefault } from "../../../../components/common/constants"; -import { FeatureCollection, GeoJsonProperties, Point } from "geojson"; +import { FeatureCollection, Point } from "geojson"; const TRUNCATE_COUNT = 800; @@ -151,7 +151,7 @@ const AbstractAndDownloadPanel: FC = () => { From 3a067461a9a387f050ce4c80d3b2376c4fec3b41 Mon Sep 17 00:00:00 2001 From: rng Date: Mon, 23 Dec 2024 12:31:40 +1100 Subject: [PATCH 11/11] Complete refactor --- .../map/mapbox/controls/menu/DateRange.tsx | 5 +++-- .../map/mapbox/controls/menu/DrawRect.tsx | 22 +++++++++++-------- .../context/detail-page-provider.tsx | 2 +- .../tab-panels/AbstractAndDownloadPanel.tsx | 16 +++++++------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/components/map/mapbox/controls/menu/DateRange.tsx b/src/components/map/mapbox/controls/menu/DateRange.tsx index 1003eb71..07ce3d82 100644 --- a/src/components/map/mapbox/controls/menu/DateRange.tsx +++ b/src/components/map/mapbox/controls/menu/DateRange.tsx @@ -131,8 +131,9 @@ const DateRange: React.FC = ({ [] ); prev.forEach((p) => { - const callback = (p as IDownloadConditionCallback).removeCallback; - callback && callback(); + const callback: IDownloadConditionCallback = + p as IDownloadConditionCallback; + callback.removeCallback && callback.removeCallback(); }); } else { const dateRangeCondition = new DateRangeCondition( diff --git a/src/components/map/mapbox/controls/menu/DrawRect.tsx b/src/components/map/mapbox/controls/menu/DrawRect.tsx index 36b830bd..1390c8e7 100644 --- a/src/components/map/mapbox/controls/menu/DrawRect.tsx +++ b/src/components/map/mapbox/controls/menu/DrawRect.tsx @@ -76,30 +76,34 @@ const DrawRect: React.FC = ({ useEffect(() => { if (map) { - const updateArea = () => { + // This function also handle delete, the reason is draw.delete works on the highlighted draw item on map + // so the below logic looks for remain valid box and set all effectively remove the item + const onCreateOrUpdate = () => { const features = mapDraw.getAll().features; - const bboxes: BBoxCondition[] = + const box: BBoxCondition[] = features ?.filter((feature) => feature.geometry.type === "Polygon") .map((feature) => { const polygon = feature.geometry as Polygon; const bbox = turf.bbox(polygon); const id = _.toString(feature.id); + // The removeCallback will be called when use click the bbox condition delete button return new BBoxCondition(id, bbox, () => mapDraw.delete(id)); }) || []; - getAndSetDownloadConditions(DownloadConditionType.BBOX, bboxes); + // In case of delete, the box already gone on map, so we just need to remove the condition + getAndSetDownloadConditions(DownloadConditionType.BBOX, box); }; map.addControl(mapDraw); - map.on("draw.create", updateArea); - map.on("draw.delete", updateArea); - map.on("draw.update", updateArea); + map.on("draw.create", onCreateOrUpdate); + map.on("draw.delete", onCreateOrUpdate); + map.on("draw.update", onCreateOrUpdate); return () => { try { - map.off("draw.create", updateArea); - map.off("draw.delete", updateArea); - map.off("draw.update", updateArea); + map.off("draw.create", onCreateOrUpdate); + map.off("draw.delete", onCreateOrUpdate); + map.off("draw.update", onCreateOrUpdate); map.removeControl(mapDraw); } catch (ignored) { /* can be ignored */ diff --git a/src/pages/detail-page/context/detail-page-provider.tsx b/src/pages/detail-page/context/detail-page-provider.tsx index 802f1937..0afa4a24 100644 --- a/src/pages/detail-page/context/detail-page-provider.tsx +++ b/src/pages/detail-page/context/detail-page-provider.tsx @@ -44,7 +44,7 @@ export const DetailPageProvider: FC = ({ ): IDownloadCondition[] => { let p: IDownloadCondition[] = []; _setDownloadConditions((prev) => { - p = prev; + p = prev.filter((condition) => condition.type === type); return prev .filter((condition) => condition.type !== type) .concat(conditions); diff --git a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx index ce41ce36..46c16044 100644 --- a/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx +++ b/src/pages/detail-page/subpages/tab-panels/AbstractAndDownloadPanel.tsx @@ -150,7 +150,13 @@ const AbstractAndDownloadPanel: FC = () => { /> { />