From e3f6bce206c430624a6d410c388dcf5141d13747 Mon Sep 17 00:00:00 2001 From: barbara-chaves Date: Wed, 20 Dec 2023 16:15:34 +0100 Subject: [PATCH 1/3] Update map plugin --- .../map/controls/settings/index.tsx | 2 - .../map/legend/item-types/timeline/index.tsx | 2 + .../map/legend/item/toolbar/index.tsx | 2 + client/src/components/ui/button.tsx | 1 + .../map/markers/story-markers/carousel.tsx | 2 + .../map/settings/basemaps/item/index.tsx | 1 + cms/config/plugins.ts | 2 +- .../api/story/content-types/story/schema.json | 30 +- .../admin/src/components/Map/index.tsx | 41 ++- .../admin/src/components/MapInputs/index.tsx | 66 +++++ .../src/components/PluginInput/index.tsx | 276 +++++++++++------- .../src/components/PluginMedia/index.tsx | 7 +- cms/src/plugins/map-field/admin/src/index.tsx | 90 +++++- .../plugins/map-field/admin/src/types.d.ts | 1 + cms/src/plugins/map-field/admin/src/types.ts | 1 + 15 files changed, 383 insertions(+), 141 deletions(-) create mode 100644 cms/src/plugins/map-field/admin/src/components/MapInputs/index.tsx create mode 100644 cms/src/plugins/map-field/admin/src/types.d.ts diff --git a/client/src/components/map/controls/settings/index.tsx b/client/src/components/map/controls/settings/index.tsx index 14c53b8..c1cfc56 100644 --- a/client/src/components/map/controls/settings/index.tsx +++ b/client/src/components/map/controls/settings/index.tsx @@ -1,5 +1,3 @@ -'use client'; - import { FC } from 'react'; import { PopoverArrow } from '@radix-ui/react-popover'; diff --git a/client/src/components/map/legend/item-types/timeline/index.tsx b/client/src/components/map/legend/item-types/timeline/index.tsx index 2cddce5..68c1ccd 100644 --- a/client/src/components/map/legend/item-types/timeline/index.tsx +++ b/client/src/components/map/legend/item-types/timeline/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import React, { useCallback, useMemo } from 'react'; import { Root, Track, Thumb } from '@radix-ui/react-slider'; diff --git a/client/src/components/map/legend/item/toolbar/index.tsx b/client/src/components/map/legend/item/toolbar/index.tsx index 990b560..203163d 100644 --- a/client/src/components/map/legend/item/toolbar/index.tsx +++ b/client/src/components/map/legend/item/toolbar/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useState } from 'react'; import { PopoverArrow } from '@radix-ui/react-popover'; diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx index 9a631a1..6601462 100644 --- a/client/src/components/ui/button.tsx +++ b/client/src/components/ui/button.tsx @@ -1,3 +1,4 @@ +'use client'; import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; diff --git a/client/src/containers/map/markers/story-markers/carousel.tsx b/client/src/containers/map/markers/story-markers/carousel.tsx index 2eacd21..9757a26 100644 --- a/client/src/containers/map/markers/story-markers/carousel.tsx +++ b/client/src/containers/map/markers/story-markers/carousel.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useState } from 'react'; import Image from 'next/image'; diff --git a/client/src/containers/map/settings/basemaps/item/index.tsx b/client/src/containers/map/settings/basemaps/item/index.tsx index e9cb9ee..162a43c 100644 --- a/client/src/containers/map/settings/basemaps/item/index.tsx +++ b/client/src/containers/map/settings/basemaps/item/index.tsx @@ -1,3 +1,4 @@ +'use client'; import { useCallback } from 'react'; import Image from 'next/image'; diff --git a/cms/config/plugins.ts b/cms/config/plugins.ts index f2ee67a..f34daf1 100644 --- a/cms/config/plugins.ts +++ b/cms/config/plugins.ts @@ -69,7 +69,7 @@ module.exports = ({ env }) => ({ }, }, published: { - url: 'http://localhost:3000/stories/{id}', + url: `${env('STRAPI_ADMIN_PREVIEW_URL')}/stories/{id}`, }, }, ], diff --git a/cms/src/api/story/content-types/story/schema.json b/cms/src/api/story/content-types/story/schema.json index c7528b9..448a08b 100644 --- a/cms/src/api/story/content-types/story/schema.json +++ b/cms/src/api/story/content-types/story/schema.json @@ -30,37 +30,31 @@ "default": "In progress", "required": true }, - "latitude": { - "max": 90, - "min": -90, - "required": true, - "type": "float" - }, - "longitude": { - "type": "float", - "required": true, - "max": 180, - "min": -180 - }, "category": { "type": "relation", "relation": "manyToOne", "target": "api::category.category", "inversedBy": "stories" }, - "bbox": { - "type": "json", - "required": true - }, "steps": { "type": "dynamiczone", "components": [ "step-layout.map-step", "step-layout.outro-step" - ] + ], + "required": true }, "active": { - "type": "boolean" + "type": "boolean", + "default": true + }, + "marker": { + "type": "customField", + "options": { + "format": "marker" + }, + "customField": "plugin::map-field.map-field", + "required": true } } } diff --git a/cms/src/plugins/map-field/admin/src/components/Map/index.tsx b/cms/src/plugins/map-field/admin/src/components/Map/index.tsx index 6462263..2c55658 100644 --- a/cms/src/plugins/map-field/admin/src/components/Map/index.tsx +++ b/cms/src/plugins/map-field/admin/src/components/Map/index.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import ReactMapGL, { Marker, NavigationControl, ViewStateChangeEvent, useMap } from 'react-map-gl'; import Media from '../PluginMedia'; -import { MarkerDragEvent, MarkerEvent } from 'react-map-gl/dist/esm/types'; +import { MarkerDragEvent, MarkerEvent, ViewState } from 'react-map-gl/dist/esm/types'; import { LocationType, MarkerType } from '../../types'; +import { useDebounce } from '../../utils/useDebounce'; const mapboxAccessToken = process.env.STRAPI_ADMIN_MAPBOX_ACCESS_TOKEN; @@ -16,6 +17,7 @@ type MapProps = { handleAddMarker: (e: mapboxgl.MapLayerMouseEvent) => void; handleDragMarker: (e: MarkerDragEvent, marker: MarkerType) => void; handleEditMarker: (marker: MarkerType) => void; + location?: LocationType; }; const Map = ({ @@ -26,6 +28,7 @@ const Map = ({ handleAddMarker, handleDragMarker, handleEditMarker, + location, }: MapProps) => { const { [id]: map } = useMap(); const [draggingMarker, setDraggingMarker] = useState(); @@ -50,10 +53,17 @@ const Map = ({ const onClickMarker = (e: MarkerEvent, marker: MarkerType) => { e.originalEvent.stopPropagation(); + if (marker.isStoryMarker) return; if (draggingMarker) return; handleEditMarker(marker); }; + const mapViewState = useMemo(() => { + if (!location) return; + const { bbox, ...vState } = location; + return { ...vState, width: 500, height: 500 }; + }, [location]); + return ( - + {marker.isStoryMarker ? ( +
+ ) : ( + + )} ); })} diff --git a/cms/src/plugins/map-field/admin/src/components/MapInputs/index.tsx b/cms/src/plugins/map-field/admin/src/components/MapInputs/index.tsx new file mode 100644 index 0000000..19ec040 --- /dev/null +++ b/cms/src/plugins/map-field/admin/src/components/MapInputs/index.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Typography, Flex, Box } from '@strapi/design-system'; +import { useMap } from 'react-map-gl'; + +type MapInputsProps = { + inputs?: [string, any][]; + handleChange: (key: string, value: number) => void; + locationType?: string; +}; + +const MapInputs = ({ inputs, handleChange, locationType }: MapInputsProps) => { + const { default: map } = useMap(); + + const onChange = (key: string, value: number) => { + if (locationType === 'map' && map) { + switch (key) { + case 'latitude': + map?.setCenter([map.getCenter().lng, value]); + break; + case 'longitude': + map?.setCenter([value, map.getCenter().lat]); + break; + case 'zoom': + map?.setZoom(value); + break; + case 'bearing': + map?.setBearing(value); + break; + case 'pitch': + map?.setPitch(value); + break; + default: + break; + } + } + handleChange(key, Number(value)); + }; + + return ( + + {!!inputs && + inputs.map(([key, value]) => ( + + + onChange(key, e.target.valueAsNumber)} + style={{ maxWidth: '125px' }} + inputMode="numeric" + pattern="\d*" + /> + + ))} + + ); +}; + +export default MapInputs; diff --git a/cms/src/plugins/map-field/admin/src/components/PluginInput/index.tsx b/cms/src/plugins/map-field/admin/src/components/PluginInput/index.tsx index 5558b05..6f964d8 100644 --- a/cms/src/plugins/map-field/admin/src/components/PluginInput/index.tsx +++ b/cms/src/plugins/map-field/admin/src/components/PluginInput/index.tsx @@ -3,7 +3,7 @@ * PluginInput * */ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { MapProvider } from 'react-map-gl'; import { useIntl } from 'react-intl'; @@ -22,20 +22,51 @@ import { } from '@strapi/design-system'; import { MediaLibraryInput } from '@strapi/plugin-upload/admin/src/components/MediaLibraryInput'; +import { useCMEditViewDataManager } from '@strapi/helper-plugin'; import 'mapbox-gl/dist/mapbox-gl.css'; import { MarkerDragEvent } from 'react-map-gl/dist/esm/types'; import Media from '../PluginMedia'; import Map from '../Map'; import { LocationType, MarkerType } from '../../types'; +import MapInputs from '../MapInputs'; const id = 'default'; -const PluginInput = ({ name, intlLabel, value, onChange }) => { +const map_location_inputs = ['longitude', 'latitude', 'zoom', 'bearing', 'pitch']; +const map_marker_inputs = ['lat', 'lng']; + +const PluginInput = ({ name, intlLabel, value, onChange, ...rest }) => { const { formatMessage } = useIntl(); + // Open the map in the story location if there is a story marker + const CMEditViewDataManager = useCMEditViewDataManager(); + const storyMarkerLocation: LocationType | undefined = useMemo(() => { + if (CMEditViewDataManager?.modifiedData?.marker) { + const parsedData = JSON.parse(CMEditViewDataManager?.modifiedData?.marker); + if (parsedData?.markers?.length) { + const storyMarker = parsedData?.markers[0]; + return { + latitude: storyMarker.lat, + longitude: storyMarker.lng, + zoom: parsedData?.location?.zoom, + bearing: parsedData?.location?.bearing, + pitch: parsedData?.location?.pitch, + padding: parsedData?.location?.padding, + bbox: parsedData?.location?.bbox, + }; + } + } + }, [CMEditViewDataManager?.modifiedData?.marker]); + + // Location type: if the map is setting the story marker in the globe (type === marker) or the location in map steps (type === map) + const locationType = rest.attribute.options?.format || 'map'; + + // Map modal + const [open, setOpen] = useState(false); + // Location - const initialState = (value && JSON.parse(value)) || null; + const initialState = (value && JSON.parse(value)) || { location: storyMarkerLocation }; const [location, setLocation] = useState(initialState?.location); const handleMoveEnd = useCallback((_location: LocationType) => { @@ -54,6 +85,11 @@ const PluginInput = ({ name, intlLabel, value, onChange }) => { id: markerId, media: null, }; + if (locationType === 'marker') { + setMarkers([{ ...newMarker, isStoryMarker: true }]); + setEditingMarker(null); + return; + } setEditingMarker(newMarker); }; @@ -71,6 +107,7 @@ const PluginInput = ({ name, intlLabel, value, onChange }) => { const handleSaveMarker = async () => { if (!editingMarker?.id) return; + if (!markers.find((marker) => marker.id === editingMarker?.id)) { setMarkers([...markers, editingMarker]); } else { @@ -86,7 +123,7 @@ const PluginInput = ({ name, intlLabel, value, onChange }) => { setEditingMarker(null); }; - const handleChangeMarker = (e) => { + const handleChangeMarker = (e: any) => { if (!editingMarker?.id) return; const { name, value } = e.target; setEditingMarker({ ...editingMarker, [name]: value }); @@ -97,7 +134,7 @@ const PluginInput = ({ name, intlLabel, value, onChange }) => { setEditingMarker(null); }; - const handleChangeMedia = (e) => { + const handleChangeMedia = (e: any) => { if (!editingMarker?.id) return; setEditingMarker({ ...editingMarker, media: e.target.value }); }; @@ -109,104 +146,145 @@ const PluginInput = ({ name, intlLabel, value, onChange }) => { const isEditing = markers.find((marker) => marker.id === editingMarker?.id); + const storyMarker = useMemo( + () => (locationType === 'marker' && markers.length ? markers[0] : undefined), + [markers, locationType] + ); + return ( - - - {formatMessage(intlLabel)} - - - - {/* Explain that this map is for setting the location, zoom, rotation and tilt of the map. */} -

- This map is for setting the location, zoom and rotation of the map. You can use the - controls in the top right to change the zoom and rotation. -

-

The same view you see here will be the same view your users will see.

-

Click on the map to add a marker. You can drag the marker to change its location.

-
- - - - -
- {editingMarker?.id && ( - setEditingMarker(null)} labelledBy="title"> - - - {isEditing ? 'Edit' : 'Add'} marker - - - - + + + {formatMessage(intlLabel)} + + + + + + {open && ( + setOpen(false)}> + + + {/* Explain that this map is for setting the location, zoom, rotation and tilt of the map. */} +

+ Click on the map to add a marker. You can drag the marker to change its location. +

+
+
+ + + - - - Media - - - - + map_location_inputs.includes(key) + ), + ]} + locationType="map" + handleChange={(k, v) => setLocation((_l) => ({ ..._l, [k]: v }))} + /> + )} + +
+ {editingMarker?.id && ( + setEditingMarker(null)} labelledBy="title"> + + + {isEditing ? 'Edit' : 'Add'} marker + + + + - - - {editingMarker?.media?.url && ( - + + + Media + + + + + + + {editingMarker?.media?.url && ( + + )} + + + + + + Delete + + ) : null + } + endActions={ + <> + + + + } + /> + + )} +
+ {locationType === 'marker' + ? !!storyMarker && ( + + map_marker_inputs.includes(key) )} - - - -
- - Delete - - ) : null - } - endActions={ - <> - - - - } - /> -
- )} -
-
+ handleChange={(k, v) => setMarkers((_m) => [{ ..._m[0], [k]: v }])} + /> + ) + : null} + + + )} + ); }; diff --git a/cms/src/plugins/map-field/admin/src/components/PluginMedia/index.tsx b/cms/src/plugins/map-field/admin/src/components/PluginMedia/index.tsx index 8d4a83a..8a6db35 100644 --- a/cms/src/plugins/map-field/admin/src/components/PluginMedia/index.tsx +++ b/cms/src/plugins/map-field/admin/src/components/PluginMedia/index.tsx @@ -40,11 +40,14 @@ const PluginMedia = ({ } }, [isDragging, playable]); + const mediaSrc = + process.env.NEXT_PUBLIC_API_URL === 'development' ? `${apiBaseUrl}${media?.url}` : media?.url; + const MediaComponent = useCallback(() => { { switch (mediaType) { case 'image': - return {name}; + return {name}; case 'video': return (