Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • fcos-suite/fcos-suite-map
1 result
Show changes
Commits on Source (28)
Showing
with 13945 additions and 3854 deletions
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint'],
rules: {
'react/prop-types': 0,
},
};
......@@ -28,4 +28,4 @@ lftp deploy:
only:
- main
script:
- lftp -e "set net:timeout 5; set net:max-retries 3; set net:reconnect-interval-base 5; open sftp://$SFTP_HOST; user $SFTP_USER $SFTP_PASSWORD; mirror -X .* -X .*/ --reverse --verbose build/ live/; bye"
- lftp -e "set net:timeout 5; set net:max-retries 3; set net:reconnect-interval-base 5; open sftp://$SFTP_HOST; user $SFTP_USER $SFTP_PASSWORD; mirror -X .* -X .*/ --reverse --verbose build/ /httpdocs/map; bye"
\ No newline at end of file
overwrite: true
schema: 'http://localhost:8000/graphql'
documents: 'src/graphql/**/*.ts'
generates:
src/generated/graphql.ts:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-graphql-request'
config:
useTypeImports: true
This diff is collapsed.
......@@ -3,8 +3,10 @@
"start": "snowpack dev",
"build": "snowpack build && npm run copy:htaccess",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
"copy:htaccess": "cp public/.htaccess build/"
"prettier:lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
"eslint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"copy:htaccess": "cp public/.htaccess build/",
"generate": "graphql-codegen --config codegen.yml"
},
"dependencies": {
"@tailwindcss/forms": "^0.3.2",
......@@ -16,11 +18,16 @@
"react-dom": "^17.0.0",
"react-leaflet": "^3.2.0",
"react-router-dom": "^5.2.0",
"react-select": "4.0.2",
"swr": "^0.5.6",
"tailwindcss": "^2.0.3",
"zustand": "^3.5.1"
},
"devDependencies": {
"@graphql-codegen/cli": "1.21.4",
"@graphql-codegen/typescript": "1.22.0",
"@graphql-codegen/typescript-graphql-request": "^3.2.0",
"@graphql-codegen/typescript-operations": "1.17.16",
"@snowpack/plugin-dotenv": "^2.1.0",
"@snowpack/plugin-postcss": "^1.3.0",
"@snowpack/plugin-react-refresh": "^2.4.0",
......@@ -29,8 +36,14 @@
"@types/react": "^17.0.6",
"@types/react-dom": "^17.0.5",
"@types/react-router-dom": "^5.1.7",
"@types/react-select": "^4.0.15",
"@types/snowpack-env": "^2.3.2",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"autoprefixer": "^10.2.4",
"eslint": "^7.26.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.23.2",
"postcss": "^8.3.0",
"postcss-cli": "^8.3.1",
"prettier": "^2.3.0",
......
......@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Fab City Hamburg Map</title>
<!-- TODO: This should be imported from the leaflet npm package, not manually from the public folder. See https://github.com/snowpackjs/snowpack/discussions/1716 -->
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<link rel="stylesheet" href="/leaflet.css" />
<script src="/leaflet.js"></script>
</head>
<body class="h-full w-full">
<div id="root" class="h-full w-full"></div>
......
......@@ -5,11 +5,10 @@ module.exports = {
src: { url: '/dist' },
},
plugins: [
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-dotenv',
'@snowpack/plugin-react-refresh',
'@snowpack/plugin-typescript',
'@snowpack/plugin-postcss',
'@snowpack/plugin-dotenv',
],
routes: [
/* Enable an SPA Fallback in development: */
......
......@@ -7,8 +7,9 @@ import Map from './Map/Map';
import SidebarCreateView from './Sidebar/SidebarCreateView';
import SidebarListView from './Sidebar/SidebarListView';
import SidebarSingleView from './Sidebar/SidebarSingleView';
import PoiLoader from './PoiLoader';
const App = () => {
const App: React.FC = () => {
const selectedPoi = useStore((state) => state.selectedPoi);
return (
......@@ -16,7 +17,11 @@ const App = () => {
<ErrorModal />
<Notification />
<div className={'flex md:flex-row-reverse flex-col h-full'}>
<Route path="/add" children={({ match }) => <Map createMode={!!match} />} />
<Route path="/add">{({ match }) => <Map createMode={!!match} />}</Route>
<Route path="/poi/:poiId">{({ match }) => <PoiLoader poiId={match?.params?.poiId as string} />}</Route>
<Route exact path="/">
<PoiLoader poiId={null} />
</Route>
<Switch>
<Route exact path="/add">
<SidebarCreateView />
......
import React from 'react';
import type { Theme } from 'react-select';
import type { NamedProps, StylesConfig } from 'react-select';
import Creatable from 'react-select/creatable';
const CreatableSelect = <OptionType, isMulti extends boolean>(props: NamedProps<OptionType, isMulti>): JSX.Element => {
const customStyles: StylesConfig<OptionType, isMulti> = {
control: (provided) => ({
...provided,
border: '0',
borderRadius: '0.5em',
boxShadow: 'none',
}),
multiValue: (provided) => ({ ...provided, borderRadius: '999px', padding: '0 3px' }),
multiValueRemove: (provided) => ({
...provided,
color: 'hsl(0, 0%, 50%)',
'&:hover': { backgroundColor: 'initial', color: 'black' },
}),
};
return (
<div>
<Creatable
theme={(theme): Theme => ({
...theme,
// @ts-expect-error: ThemeConfig type from definitely-typed is not complete
borderRadius: '0.5em',
colors: {
...theme.colors,
primary25: '#C7D2FE',
},
})}
styles={customStyles}
name="pois"
className="hover:border-opacity-40 rounded-lg w-full border-2 border-black border-opacity-20 focus-within:border-indigo-300 focus-within:ring focus-within:ring-indigo-200 focus-within:ring-opacity-50 mt-1"
{...props}
/>
</div>
);
};
export default CreatableSelect;
......@@ -3,9 +3,7 @@ import React from 'react';
import { useStore } from '../hooks';
import Modal from './Modal';
interface Props {}
const ErrorModal: React.FC<Props> = () => {
const ErrorModal: React.FC = () => {
const error = useStore((state) => state.error);
const icons: { [index: string]: JSX.Element } = {
alert: <AlertIcon className="h-6 w-6 text-red-600" />,
......
......@@ -5,17 +5,19 @@ import { validateFile } from '../../util/file';
import TextInput from './TextInput';
import FileInput from './FileInput';
import TextAreaInput from './TextAreaInput';
import { useStore, usePoiData } from '../../hooks';
import { usePoiData, useStore } from '../../hooks';
import { useHistory } from 'react-router-dom';
import CoordinateInput from './CoordinateInput';
import TagInput from './TagInput';
import { removeDuplicateObjects } from '../../util/array';
import { createPoi, createTags } from '../../graphql/mutations';
import Spinner from '../Spinner';
import type { CreatePoiMutationMutationVariables, Mutation } from '../../generated/graphql';
import SelectInput from './SelectInput';
interface Props {}
type StringSelectOption = { label: string; value: string };
const AddPoiForm: React.FC<Props> = () => {
const AddPoiForm: React.FC = () => {
const [formData, setFormData] = useState<PointOfInterestFormData>({
lat: 0,
lng: 0,
......@@ -25,6 +27,7 @@ const AddPoiForm: React.FC<Props> = () => {
description: '',
website: '',
category: '',
relationStatus: '',
image: null,
tags: [],
});
......@@ -35,6 +38,8 @@ const AddPoiForm: React.FC<Props> = () => {
const history = useHistory();
const { data } = usePoiData();
const [tagOptions, setTagOptions] = useState<Tag[]>([]);
const [categoryOptions, setCatgeoryOptions] = useState<StringSelectOption[]>([]);
const [relationStatusOptions, setRelationStatusOptions] = useState<StringSelectOption[]>([]);
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
const setNotification = useStore((state) => state.setNotification);
......@@ -57,33 +62,37 @@ const AddPoiForm: React.FC<Props> = () => {
setIsLoading(true);
try {
let newTags: Tag[] = [];
let newTagIdsResponse: number[] = [];
let oldTags: Tag[] = [];
const newTags: Tag[] = [];
let newTagIdsResponse: string[] = [];
const oldTags: Tag[] = [];
selectedTags.forEach((tag) => (tag.id === 'draft' ? newTags.push(tag) : oldTags.push(tag)));
if (newTags.length) {
const tagsRes = await fetcher(createTags, {
const tagsRes: Mutation = await fetcher(createTags, {
tags: newTags.map(({ displayName, color }) => ({
displayName,
color,
})),
});
if (tagsRes.createTags.length > 0) {
tagsRes.createTags.map((newTagResponse: { id: string }) => {
newTagIdsResponse.push(Number(newTagResponse.id));
});
if (tagsRes.createTags && tagsRes.createTags.length > 0) {
newTagIdsResponse = tagsRes.createTags
.filter((newTagResponse) => newTagResponse?.id)
.map((newTagResponse) => {
return (newTagResponse as Tag).id;
});
}
}
const finalData = { ...formData, tagIds: [...newTagIdsResponse, ...oldTags.map((oldTag) => Number(oldTag.id))] };
const finalData: CreatePoiMutationMutationVariables = {
...formData,
tagIds: [...newTagIdsResponse, ...oldTags.map((oldTag) => oldTag.id)],
};
const res = await fetcher(createPoi, finalData);
const res: Mutation = await fetcher(createPoi, finalData);
if (res.createPoi) {
setNotification({
title: 'Ort hinzugefügt',
text:
'Ort wurde erfolgreich hinzugefügt. Bitte überprüfe deine E-Mails und klicke dort auf den Link um deine Mail-Adresse zu verifizieren.',
text: 'Ort wurde erfolgreich hinzugefügt. Bitte überprüfe deine E-Mails und klicke dort auf den Link um deine Mail-Adresse zu verifizieren.',
type: 'success',
});
history.push('/');
......@@ -110,6 +119,15 @@ const AddPoiForm: React.FC<Props> = () => {
});
const tags = removeDuplicateObjects(tagsWithDuplicates, 'id');
setTagOptions(tags);
const relationStatuses = removeDuplicateObjects(data, 'relationStatus')
.filter((poi) => !!poi.relationStatus)
.map((poi) => ({ label: poi.relationStatus, value: poi.relationStatus }));
setRelationStatusOptions(relationStatuses);
const categories = removeDuplicateObjects(data, 'category')
.filter((poi) => !!poi.category)
.map((poi) => ({ label: poi.category, value: poi.category }));
setCatgeoryOptions(categories);
}
}, [data]);
......@@ -129,10 +147,6 @@ const AddPoiForm: React.FC<Props> = () => {
if (draftPoi) setFormData({ ...formData, lat: draftPoi[0], lng: draftPoi[1] });
}, [draftPoi]);
useEffect(() => {
console.log('Form Data', formData);
}, [formData]);
return (
<form className="flex-1 flex flex-col justify-between" onSubmit={handleSubmit} ref={formRef}>
<div className="flex flex-col">
......@@ -144,12 +158,31 @@ const AddPoiForm: React.FC<Props> = () => {
/>
<TagInput label={'Tags'} tags={selectedTags} options={tagOptions} onTagsChange={setSelectedTags} />
<TextInput label={'Name des Orts'} name={'name'} value={formData.name} onChange={handleInputChange} required />
<TextInput
<SelectInput
label={'Kategorie'}
required
placeholder={'Auswählen...'}
name={'category'}
value={formData.category}
onChange={handleInputChange}
options={categoryOptions}
onChange={(selectedOption) =>
setFormData((prev) => ({
...prev,
category: selectedOption ? (selectedOption as StringSelectOption).value : '',
}))
}
/>
<SelectInput
label={'Verhältnis zum Fab City Hamburg e.V.'}
required
name={'relationStatus'}
placeholder={'Auswählen...'}
options={relationStatusOptions}
onChange={(selectedOption) =>
setFormData((prev) => ({
...prev,
relationStatus: selectedOption ? (selectedOption as StringSelectOption).value : '',
}))
}
/>
<TextInput
label={'Anschrift'}
......
......@@ -18,7 +18,7 @@ const CoordinateInput: React.FC<Props> = ({ label, text, value, required }) => {
</span>
)}
<label className="flex mt-1 mb-4 p-2 items-center w-full rounded-lg border-2 border-black border-opacity-20 hover:border-opacity-40 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
<CheckIcon className={`${Boolean(value.filter(Boolean).length) ? 'text-indigo-500' : 'text-gray-400'} mr-2`} />
<CheckIcon className={`${value.filter(Boolean).length ? 'text-indigo-500' : 'text-gray-400'} mr-2`} />
<input readOnly type="text" name={'lat'} value={value[0]} className="hidden" required={required}></input>
<input readOnly type="text" name={'lng'} value={value[1]} className="hidden" required={required}></input>
{!!text && <span className="form-label">{text}</span>}
......
......@@ -23,7 +23,7 @@ const FileInput: React.FC<Props> = ({ name, label, value, onChange, ...inputProp
: ' bg-indigo-500 text-white border-opacity-0 hover:bg-indigo-600 text-md'
}`}
>
{!!value ? (
{value ? (
<>
<PaperClipIcon size={18} className="flex-shrink-0 mr-2" />
{value.name}
......
import React from 'react';
import type { NamedProps } from 'react-select';
import CreatableSelect from '../CreatableSelect';
interface Props<OptionType, isMulti extends boolean> extends NamedProps<OptionType, isMulti> {
label: string;
name: string;
required?: boolean;
}
const SelectInput = <OptionType, isMulti extends boolean>({
name,
label,
value,
onChange,
...inputProps
}: Props<OptionType, isMulti>): JSX.Element => {
return (
<label className="block mb-4">
{!!label && (
<span className="form-label">
{label}
{inputProps?.required && `*`}
</span>
)}
<CreatableSelect
name={name}
value={value}
className="p-0 form-input form-input-custom"
onChange={onChange}
{...inputProps}
/>
</label>
);
};
export default SelectInput;
......@@ -11,8 +11,7 @@ interface Props {
required?: boolean;
}
const TagInput = ({ label, tags, options, onTagsChange, required }: Props) => {
// const [allTags, setAllTags] = useState<TagType[]>([]);
const TagInput: React.FC<Props> = ({ label, tags, options, onTagsChange, required }: Props) => {
// All options
const [tagOptions, setTagOptions] = useState<TagType[]>([]);
// The text input
......@@ -81,7 +80,7 @@ const TagInput = ({ label, tags, options, onTagsChange, required }: Props) => {
<div className="flex flex-wrap items-center flex-1 p-2">
{tags.map((selectedTag) => (
<Tag
key={`selected${selectedTag.id}`}
key={`selected${selectedTag.color}`}
onClickDelete={() => handleClickDeleteTag(selectedTag)}
color={selectedTag.color}
>
......@@ -100,7 +99,7 @@ const TagInput = ({ label, tags, options, onTagsChange, required }: Props) => {
}}
type={'text'}
value={draftEntry}
className={`w-16 block p-0 flex-1 rounded-lg border-0 focus:outline-none focus:ring-0 ${
className={`form-input w-16 block p-0 flex-1 rounded-lg border-0 focus:outline-none focus:ring-0 ${
tags.length ? '' : 'px-3'
}`}
/>
......@@ -117,7 +116,7 @@ const TagInput = ({ label, tags, options, onTagsChange, required }: Props) => {
return (
<li
key={`${option.id}`}
onMouseDown={(e) => {
onMouseDown={() => {
handleSelectTag(option);
}}
className="w-full p-1 hover:bg-gray-100"
......
......@@ -20,7 +20,7 @@ const TextAreaInput: React.FC<Props> = ({ name, label, value, onChange, ...textA
<textarea
name={name}
value={value}
className="form-input"
className="form-textarea form-input-custom"
rows={3}
onChange={onChange}
{...textAreaProps}
......
import React, { MutableRefObject, useRef } from 'react';
import React from 'react';
interface Props {
label: string;
......@@ -18,7 +18,14 @@ const TextInput: React.FC<Props> = ({ name, label, value, onChange, type = 'text
{inputProps?.required && `*`}
</span>
)}
<input type={type} name={name} value={value} className="form-input" onChange={onChange} {...inputProps} />
<input
type={type}
name={name}
value={value}
className="form-input form-input-custom"
onChange={onChange}
{...inputProps}
/>
</label>
);
};
......
import type { LatLngTuple } from 'leaflet';
import { divIcon, DivIconOptions } from 'leaflet';
import React, { useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { MapContainer, Marker, TileLayer } from 'react-leaflet';
import { usePoiData, useStore } from '../../hooks';
import { useStore } from '../../hooks';
import MapViewController from './MapViewController';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import MapLayerControl from './MapLayerControl';
import { useHistory } from 'react-router-dom';
interface Props {
createMode?: boolean;
......@@ -25,54 +28,70 @@ export const Map: React.FC<Props> = ({ createMode }) => {
() => divIcon({ ...iconProps, iconSize: [30, 40], iconAnchor: [15, 40], className: 'marker-green' }),
[iconProps],
);
const { data } = usePoiData();
const { data } = useFilteredPoiData();
const draftPoi = useStore((state) => state.draftPoi);
const [mapBounds, setMapBounds] = useState<Array<LatLngTuple>>([DEFAULT_CENTER]);
const hoveredPoi = useStore((state) => state.hoveredPoi);
const setHoveredPoi = useStore((state) => state.setHoveredPoi);
const selectedPoi = useStore((state) => state.selectedPoi);
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
const history = useHistory();
const selectedLatlng: LatLngTuple | undefined = selectedPoi ? [selectedPoi?.lat, selectedPoi?.lng] : undefined;
useEffect(() => {
let newBounds = [DEFAULT_CENTER];
if (selectedPoi) {
newBounds = [[selectedPoi.lat, selectedPoi.lng] as LatLngTuple];
} else if (data?.length) {
newBounds = data?.map((poi) => [poi.lat, poi.lng] as LatLngTuple) || [];
}
setMapBounds(newBounds);
}, [JSON.stringify(data), selectedPoi]);
return (
<MapContainer id={'mapid'} className={'h-full w-full z-0'} center={DEFAULT_CENTER} zoom={13} scrollWheelZoom={true}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}"
id="mapbox/streets-v11"
tileSize={512}
accessToken={import.meta.env.SNOWPACK_PUBLIC_MAPBOX_TOKEN}
zoomOffset={-1}
maxZoom={18}
/>
<MapViewController center={selectedLatlng ?? DEFAULT_CENTER} zoom={13} createPoiMode={createMode} />
{/* Single marker when creating a new POI */}
{!!(createMode && draftPoi) && <Marker icon={greenLargeIcon} position={draftPoi} />}
{/* Single marker when POI is selected */}
{!!(selectedPoi && selectedLatlng && !createMode) && <Marker icon={largeIcon} position={selectedLatlng} />}
{/* Multiple markers, when no POI is selected */}
{!selectedPoi &&
!createMode &&
data?.map((poi) => {
const poiLatLng: LatLngTuple = [poi.lat, poi.lng];
return (
<Marker
icon={hoveredPoi?.id === poi.id ? largeIcon : icon}
opacity={hoveredPoi?.id === poi.id ? 1 : 0.7}
key={poi.id}
position={poiLatLng}
eventHandlers={{
click: () => setSelectedPoi(poi),
mouseover: () => {
setHoveredPoi(poi);
},
mouseout: () => {
setHoveredPoi(null);
},
}}
/>
);
})}
</MapContainer>
<div className="relative h-full w-full z-0">
{!createMode && !selectedPoi && <MapLayerControl />}
<MapContainer id={'mapid'} className={'h-full w-full z-0'} scrollWheelZoom={true}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}"
id="mapbox/streets-v11"
tileSize={512}
accessToken={import.meta.env.SNOWPACK_PUBLIC_MAPBOX_TOKEN}
zoomOffset={-1}
maxZoom={18}
/>
<MapViewController bounds={mapBounds} createPoiMode={createMode} />
{/* Single marker when creating a new POI */}
{!!(createMode && draftPoi) && <Marker icon={greenLargeIcon} position={draftPoi} />}
{/* Single marker when POI is selected */}
{!!(selectedPoi && selectedLatlng && !createMode) && <Marker icon={largeIcon} position={selectedLatlng} />}
{/* Multiple markers, when no POI is selected */}
{!selectedPoi &&
!createMode &&
data?.map((poi) => {
const poiLatLng: LatLngTuple = [poi.lat, poi.lng];
return (
<Marker
icon={hoveredPoi?.id === poi.id ? largeIcon : icon}
opacity={hoveredPoi?.id === poi.id ? 1 : 0.7}
key={poi.id}
position={poiLatLng}
eventHandlers={{
click: () => {
history.push(`/poi/${String(poi.id)}`);
},
mouseover: () => {
setHoveredPoi(poi);
},
mouseout: () => {
setHoveredPoi(null);
},
}}
/>
);
})}
</MapContainer>
</div>
);
};
......
import React, { useState } from 'react';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import { usePoiData } from '../../hooks';
import { ChevronDownOutline as DownIcon } from 'heroicons-react';
const MapLayerControl: React.FC = () => {
const { data } = usePoiData();
const { filterCategories, setFilterCategories } = useFilteredPoiData();
const layers = Array.from(new Set(data?.map((poi) => poi.category)));
const [isOpen, setIsOpen] = useState(false);
const onChangeCheckbox = (state: boolean, layer: string) => {
if (state) {
setFilterCategories([...filterCategories, layer]);
} else {
setFilterCategories(filterCategories.filter((cat) => cat !== layer));
}
};
return (
<div className="border-2 border-black w-52 border-opacity-20 rounded-lg absolute right-0 bottom-0 mb-4 mr-4 bg-white p-4 z-10">
<div className={`flex justify-between cursor-pointer ${isOpen ? 'mb-2' : ''}`} onClick={() => setIsOpen(!isOpen)}>
<h3 className="text-base font-semibold text-gray-900">Kategorien:</h3>
<DownIcon
className={`w-6 h-6 text-gray-400 transform transition duration-300 ${isOpen ? 'rotate-180' : 'rotate-0'}`}
/>
</div>
{isOpen &&
layers.map((layer) => {
return (
<div key={layer} className="my-2">
<label className="inline-flex items-center">
<input
type="checkbox"
className="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50"
checked={!!filterCategories?.find((cat) => cat === layer)}
onChange={(e) => onChangeCheckbox(e.target.checked, layer)}
/>
<span className="ml-2">{layer}</span>
</label>
</div>
);
})}
</div>
);
};
export default MapLayerControl;
......@@ -4,12 +4,11 @@ import { useMap, useMapEvent } from 'react-leaflet';
import { useStore } from '../../hooks';
interface Props {
center: LatLngTuple;
zoom: number;
bounds: Array<LatLngTuple>;
createPoiMode?: boolean;
}
const MapViewController: React.FC<Props> = ({ center, zoom, createPoiMode }) => {
const MapViewController: React.FC<Props> = ({ bounds, createPoiMode }) => {
const setDraftPoi = useStore((state) => state.setDraftPoi);
const map = useMap();
......@@ -20,8 +19,8 @@ const MapViewController: React.FC<Props> = ({ center, zoom, createPoiMode }) =>
});
useEffect(() => {
map.setView(center, zoom);
}, [center, zoom]);
map.fitBounds(bounds, { maxZoom: 16 });
}, [bounds]);
return <></>;
};
......