diff --git a/app/scripts/components/common/card-sources.tsx b/app/scripts/components/common/card-sources.tsx index d592a1c68..9d9cf6cca 100644 --- a/app/scripts/components/common/card-sources.tsx +++ b/app/scripts/components/common/card-sources.tsx @@ -1,8 +1,7 @@ import React from 'react'; import styled from 'styled-components'; -import { Link } from 'react-router-dom'; import { listReset } from '@devseed-ui/theme-provider'; -import { TaxonomyItem } from '$types/veda'; +import { LinkProperties, TaxonomyItem } from '$types/veda'; import { FilterActions } from '$components/common//catalog/utils'; const SourcesUl = styled.ul` @@ -23,11 +22,12 @@ interface SourcesListProps { sources?: TaxonomyItem[]; onSourceClick?: (v: string) => void; rootPath?: string; + linkProperties?: LinkProperties; } export function CardSourcesList(props: SourcesListProps) { - const { sources, onSourceClick, rootPath } = props; - + const { sources, onSourceClick, linkProperties, rootPath } = props; + const { LinkElement, pathAttributeKeyName } = linkProperties as { LinkElement: React.ElementType, pathAttributeKeyName: string }; if (!sources?.length) return null; // No link rendering @@ -50,19 +50,19 @@ export function CardSourcesList(props: SourcesListProps) { {sources.map((source) => (
  • - { e.preventDefault(); onSourceClick(source.id); }} > {source.name} - +
  • ))}
    diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index ce98fac0b..74e705cd1 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -1,6 +1,6 @@ -import React, { lazy, MouseEventHandler, ComponentType } from 'react'; +import React, { lazy, MouseEventHandler } from 'react'; import styled, { css } from 'styled-components'; -import { format } from 'date-fns'; +import format from 'date-fns/format'; import { CollecticonExpandTopRight } from '@devseed-ui/collecticons'; import { glsp, @@ -25,6 +25,7 @@ import HorizontalInfoCard, { import { variableBaseType, variableGlsp } from '$styles/variable-utils'; import { ElementInteractive } from '$components/common/element-interactive'; import { Figure } from '$components/common/figure'; +import { LinkProperties } from '$types/veda'; type CardType = 'classic' | 'cover' | 'featured' | 'horizontal-info'; @@ -230,12 +231,6 @@ export function ExternalLinkFlag() { ); } -export interface LinkProperties { - LinkElement: string | ComponentType | undefined; - pathAttributeKeyName: string; - onLinkClick?: MouseEventHandler; -} - export interface LinkWithPathProperties extends LinkProperties { linkTo: string; } @@ -255,16 +250,17 @@ export interface CardComponentBaseProps { footerContent?: JSX.Element; hideExternalLinkBadge?: boolean; onCardClickCapture?: MouseEventHandler; + onClick?: MouseEventHandler; } -// @TODO: Consolidate these props when the instance adapts the new syntax +// @TODO: Created because GHG uses the card component directly and passes in "linkTo" prop. Consolidate these props when the instance adapts the new syntax // Specifically: https://github.com/US-GHG-Center/veda-config-ghg/blob/develop/custom-pages/news-and-events/component.tsx#L108 export interface CardComponentPropsDeprecated extends CardComponentBaseProps { linkTo: string; - onLinkClick?: MouseEventHandler; } + export interface CardComponentProps extends CardComponentBaseProps { - linkProperties: LinkWithPathProperties; + linkProperties?: LinkWithPathProperties; } type CardComponentPropsType = CardComponentProps | CardComponentPropsDeprecated; @@ -291,40 +287,37 @@ function CardComponent(props: CardComponentPropsType) { parentTo, footerContent, hideExternalLinkBadge, - onCardClickCapture + onCardClickCapture, + onClick, } = props; // @TODO: This process is not necessary once all the instances adapt the linkProperties syntax // Consolidate them to use LinkProperties only - let linkProperties: LinkWithPathProperties; + let linkProperties: LinkWithPathProperties | undefined; if (hasLinkProperties(props)) { // Handle new props with linkProperties const { linkProperties: linkPropertiesProps } = props; linkProperties = linkPropertiesProps; } else { - const { linkTo, onLinkClick } = props; - linkProperties = { + const { linkTo } = props; + linkProperties = linkTo ? { linkTo, - onLinkClick, pathAttributeKeyName: 'to', LinkElement: SmartLink - }; + } : undefined; } - const isExternalLink = /^https?:\/\//.test(linkProperties.linkTo); + const isExternalLink = linkProperties ? /^https?:\/\//.test(linkProperties.linkTo) : false; return ( {cardType !== 'horizontal-info' && ( <> @@ -340,7 +333,7 @@ function CardComponent(props: CardComponentPropsType) { parentTo && tagLabels.map((label) => ( diff --git a/app/scripts/components/common/catalog/catalog-card.tsx b/app/scripts/components/common/catalog/catalog-card.tsx index e9a85a751..944507108 100644 --- a/app/scripts/components/common/catalog/catalog-card.tsx +++ b/app/scripts/components/common/catalog/catalog-card.tsx @@ -1,27 +1,18 @@ -import React from 'react'; -import styled, { css } from 'styled-components'; -import { - CollecticonPlus, - CollecticonTickSmall, - iconDataURI -} from '@devseed-ui/collecticons'; -import { glsp, themeVal } from '@devseed-ui/theme-provider'; - -import { Card, LinkProperties } from '../card'; -import { CardMeta, CardTopicsList } from '../card/styles'; -import { DatasetClassification } from '../dataset-classification'; -import { CardSourcesList } from '../card-sources'; -import TextHighlight from '../text-highlight'; +import React from "react"; +import styled, { css } from "styled-components"; +import { CollecticonPlus, CollecticonTickSmall, iconDataURI } from "@devseed-ui/collecticons"; +import { glsp, themeVal } from "@devseed-ui/theme-provider"; +import { Card } from "../card"; +import { CardMeta, CardTopicsList } from "../card/styles"; +import { DatasetClassification } from "../dataset-classification"; +import { CardSourcesList } from "../card-sources"; +import TextHighlight from "../text-highlight"; import { getDatasetDescription, getMediaProperty } from './utils'; -import { DatasetData, DatasetLayer } from '$types/veda'; -import { getDatasetPath } from '$utils/routes'; -import { - TAXONOMY_SOURCE, - TAXONOMY_TOPICS, - getAllTaxonomyValues, - getTaxonomy -} from '$utils/veda-data/taxonomies'; -import { Pill } from '$styles/pill'; +import { LinkProperties } from '$types/veda'; +import { DatasetData, DatasetLayer } from "$types/veda"; +import { getDatasetPath } from "$utils/routes"; +import { TAXONOMY_SOURCE, TAXONOMY_TOPICS, getAllTaxonomyValues, getTaxonomy } from "$utils/veda-data/taxonomies"; +import { Pill } from "$styles/pill"; interface CatalogCardProps { dataset: DatasetData; @@ -31,7 +22,7 @@ interface CatalogCardProps { selected?: boolean; onDatasetClick?: () => void; pathname?: string; - linkProperties: LinkProperties; + linkProperties?: LinkProperties; } const CardSelectable = styled(Card)<{ @@ -142,10 +133,11 @@ export const CatalogCard = (props: CatalogCardProps) => { overline={ - + } linkLabel='View dataset' + onClick={handleClick} title={ {title} @@ -179,11 +171,7 @@ export const CatalogCard = (props: CatalogCardProps) => { ) : null} } - linkProperties={{ - ...linkProperties, - linkTo: linkTo, - onLinkClick: handleClick - }} + {...(linkProperties ? {linkProperties: {...linkProperties, linkTo: linkTo}} : {})} /> ); }; diff --git a/app/scripts/components/common/catalog/catalog-content.tsx b/app/scripts/components/common/catalog/catalog-content.tsx index 6240116f8..dcac02395 100644 --- a/app/scripts/components/common/catalog/catalog-content.tsx +++ b/app/scripts/components/common/catalog/catalog-content.tsx @@ -3,13 +3,13 @@ import styled from 'styled-components'; import { glsp, themeVal } from '@devseed-ui/theme-provider'; import TextHighlight from '../text-highlight'; import { CollecticonDatasetLayers } from '../icons/dataset-layers'; -import { LinkProperties } from '../card'; import { prepareDatasets } from './prepare-datasets'; import FiltersControl from './filters-control'; import { CatalogCard } from './catalog-card'; import CatalogTagsContainer from './catalog-tags'; import { FilterActions } from './utils'; +import { LinkProperties } from '$types/veda'; import { DatasetData, DatasetDataWithEnhancedLayers } from '$types/veda'; import { CardList } from '$components/common/card/styles'; import EmptyHub from '$components/common/empty-hub'; @@ -264,7 +264,6 @@ function CatalogContent({ selectable={true} selected={selectedIds.includes(datasetLayer.id)} onDatasetClick={() => onCardSelect(datasetLayer.id, currentDataset)} - linkProperties={linkProperties} pathname={pathname} /> diff --git a/app/scripts/components/common/catalog/index.tsx b/app/scripts/components/common/catalog/index.tsx index f4dddfc52..bff33ded7 100644 --- a/app/scripts/components/common/catalog/index.tsx +++ b/app/scripts/components/common/catalog/index.tsx @@ -1,9 +1,8 @@ import React from 'react'; import styled from 'styled-components'; import { themeVal } from '@devseed-ui/theme-provider'; -import { LinkProperties } from '../card'; import CatalogContent from './catalog-content'; -import { DatasetData } from '$types/veda'; +import { DatasetData, LinkProperties } from '$types/veda'; import { useSlidingStickyHeaderProps } from '$components/common/layout-root/useSlidingStickyHeaderProps'; diff --git a/app/scripts/components/common/element-interactive.js b/app/scripts/components/common/element-interactive.js index 497679951..6deebd1f8 100644 --- a/app/scripts/components/common/element-interactive.js +++ b/app/scripts/components/common/element-interactive.js @@ -73,7 +73,13 @@ const InteractiveLink = styled.a` */ export const ElementInteractive = React.forwardRef( function ElementInteractiveFwd(props, ref) { - const { linkProps = {}, linkLabel = 'View', children, ...rest } = props; + const { + linkProps = {}, + linkLabel = 'View', + children, + onClick, + ...rest + } = props; const [isStateOver, setStateOver] = useState(false); const [isStateActive, setStateActive] = useState(false); const [isStateFocus, setStateFocus] = useState(false); @@ -92,6 +98,7 @@ export const ElementInteractive = React.forwardRef( setStateOver(false); setStateActive(false); }, [])} + onClick={onClick} > {children} { const date = new Date(d[dateProperty ?? '']); const topics = getTaxonomy(d, TAXONOMY_TOPICS)?.values; - + const linkProperties = { + pathAttributeKeyName: 'to', + LinkElement: SmartLink + }; return ( )} diff --git a/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx b/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx index 9d5a5ae18..9f2a7814d 100644 --- a/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx +++ b/app/scripts/components/common/map/controls/aoi/custom-aoi-control.tsx @@ -76,15 +76,25 @@ function CustomAoI({ const [selectedState, setSelectedState] = useState(''); const [presetIds, setPresetIds] = useState([]); const [fileUploadedIds, setFileUplaodedIds] = useState([]); + const [updated, forceUpdate] = useState(0); // @NOTE: Needed so that this component re-renders to when the draw selection changes from feature to point. + const [isAreaSelected, setAreaSelected] = useState(false); + const [isPointSelected, setPointSelected] = useState(false); const [selectedForEditing, setSelectedForEditing] = useAtom(selectedForEditingAtom); const { onUpdate, isDrawing, setIsDrawing, features } = useAois(); const aoiDeleteAll = useSetAtom(aoiDeleteAllAtom); - // Needed so that this component re-renders to when the draw selection changes - // from feature to point. - const [, forceUpdate] = useState(0); + // @NOTE: map?._drawControl?.getSelected() needs access to mapboxgl draw context store, + // but the function gets called before mapboxdraw store is initialized (before being added to map) resulting in an error + useEffect(() => { + if (!map) return; + + const mbDraw = map?._drawControl; + setAreaSelected(!!(mbDraw?.getSelected().features.length)); + setPointSelected(!!(mbDraw?.getSelectedPoints()?.features.length)); + }, [map, updated]); + useEffect(() => { const mbDraw = map?._drawControl; if (!mbDraw) return; @@ -220,6 +230,7 @@ function CustomAoI({ // selected, the trash method doesn't do anything. So, in this case, we // trigger the delete for the whole feature. const selectedFeatures = mbDraw.getSelected()?.features; + if ( mbDraw.getMode() === DIRECT_SELECT && selectedFeatures.length && @@ -248,9 +259,6 @@ function CustomAoI({ mbDraw.trash(); }, [features, aoiDeleteAll, map]); - const isAreaSelected = !!map?._drawControl?.getSelected().features.length; - const isPointSelected = - !!map?._drawControl.getSelectedPoints().features.length; const hasFeatures = !!features.length; return ( @@ -330,21 +338,19 @@ export default function CustomAoIControl({ disableReason?: React.ReactNode; }) { const { main } = useMaps(); - const { isDrawing } = useAois(); - // Start/stop the drawing. + // Start the drawing mode when isDrawing is true + // There's no need to switch back to 'simple_select' mode when !isDrawing + // as Mapbox Draw handles this internally when the drawing is completed useEffect(() => { - // Property was added to access draw control. - const mbDraw = main?._drawControl; + if (!main) return; + const mbDraw = main._drawControl; + if (!mbDraw) return; if (isDrawing) { mbDraw.changeMode(DRAW_POLYGON); - } else { - mbDraw.changeMode(SIMPLE_SELECT, { - featureIds: mbDraw.getSelectedIds() - }); } }, [main, isDrawing]); @@ -354,5 +360,7 @@ export default function CustomAoIControl({ position: 'top-left' } ); + return null; } + diff --git a/app/scripts/components/common/map/controls/aoi/index.tsx b/app/scripts/components/common/map/controls/aoi/index.tsx index 3f8a4b2da..808cc9164 100644 --- a/app/scripts/components/common/map/controls/aoi/index.tsx +++ b/app/scripts/components/common/map/controls/aoi/index.tsx @@ -2,14 +2,18 @@ import MapboxDraw from '@mapbox/mapbox-gl-draw'; import StaticMode from '@mapbox/mapbox-gl-draw-static-mode'; import { useTheme } from 'styled-components'; import { useAtomValue } from 'jotai'; -import { useRef } from 'react'; import { useControl } from 'react-map-gl'; +import type { MapRef } from 'react-map-gl'; import useAois from '../hooks/use-aois'; import { aoisFeaturesAtom } from './atoms'; import { computeDrawStyles } from './style'; type DrawControlProps = MapboxDraw.DrawOptions; +interface ExtendedMapRef extends MapRef { + _drawControl?: MapboxDraw; +} + export const STATIC_MODE = 'static_mode'; export const SIMPLE_SELECT = 'simple_select'; export const DIRECT_SELECT = 'direct_select'; @@ -30,13 +34,12 @@ const customDirectSelect = { export default function DrawControl(props: DrawControlProps) { const theme = useTheme(); - const control = useRef(); const aoisFeatures = useAtomValue(aoisFeaturesAtom); const { onUpdate, onDelete, onSelectionChange, onDrawModeChange } = useAois(); - useControl( + const drawControl = useControl( () => { - control.current = new MapboxDraw({ + const control = new MapboxDraw({ displayControlsDefault: false, styles: computeDrawStyles(theme), modes: { @@ -47,23 +50,25 @@ export default function DrawControl(props: DrawControlProps) { }, ...props }); - return control.current; + return control; }, - ({ map }: { map: any }) => { - map._drawControl = control.current; + ({ map }) => { + // We're making the controls available on the map instance for later use throughout + // the app (e.g in the CustomAoIControl) + (map as ExtendedMapRef)._drawControl = drawControl; map.on('draw.create', onUpdate); map.on('draw.update', onUpdate); map.on('draw.delete', onDelete); map.on('draw.selectionchange', onSelectionChange); map.on('draw.modechange', onDrawModeChange); map.on('load', () => { - control.current?.set({ + drawControl.set({ type: 'FeatureCollection', features: aoisFeatures }); }); }, - ({ map }: { map: any }) => { + ({ map }) => { map.off('draw.create', onUpdate); map.off('draw.update', onUpdate); map.off('draw.delete', onDelete); @@ -76,4 +81,4 @@ export default function DrawControl(props: DrawControlProps) { ); return null; -} +} \ No newline at end of file diff --git a/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx b/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx index ff50f88a6..925e8d1b6 100644 --- a/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx +++ b/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx @@ -54,4 +54,4 @@ export default function useThemedControl( useControl(() => new ThemedControl(), opts); return null; -} +} \ No newline at end of file diff --git a/app/scripts/components/common/map/style-generators/basemap.tsx b/app/scripts/components/common/map/style-generators/basemap.tsx index b5e037b98..ad866f7f5 100644 --- a/app/scripts/components/common/map/style-generators/basemap.tsx +++ b/app/scripts/components/common/map/style-generators/basemap.tsx @@ -33,7 +33,6 @@ export function Basemap({ boundariesOption = true }: BasemapProps) { const { updateStyle } = useMapStyle(); - const [baseStyle, setBaseStyle] = useState