mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
implemented searchbar and reorganized map controls
This commit is contained in:
parent
00daae6b57
commit
25ec60b3a1
91
package-lock.json
generated
91
package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@types/offscreencanvas": "^2019.7.1",
|
||||
"axios": "^1.6.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
@ -741,6 +742,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.14",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
|
||||
@ -774,6 +780,16 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
|
||||
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.4",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bail": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||
@ -1027,6 +1043,17 @@
|
||||
"integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
|
||||
@ -1337,6 +1364,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
@ -2085,6 +2120,38 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
@ -3554,6 +3621,25 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -4634,6 +4720,11 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@types/offscreencanvas": "^2019.7.1",
|
||||
"axios": "^1.6.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
|
||||
@ -6,7 +6,7 @@ import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
|
||||
import { useItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
|
||||
import { useEffect } from 'react'
|
||||
import { ItemFormPopup } from './Subcomponents/ItemFormPopup'
|
||||
import { useFilterTags, useIsLayerVisible, useSearchPhrase } from './hooks/useFilter'
|
||||
import { useFilterTags, useIsLayerVisible } from './hooks/useFilter'
|
||||
import { useGetItemTags } from './hooks/useTags'
|
||||
import { useAddMarker, useAddPopup, useLeafletRefs } from './hooks/useLeafletRefs'
|
||||
import { Popup } from 'leaflet'
|
||||
@ -47,7 +47,6 @@ export const Layer = ( {
|
||||
|
||||
let location = useLocation();
|
||||
|
||||
const searchPhrase = useSearchPhrase();
|
||||
|
||||
const map = useMap();
|
||||
|
||||
@ -64,7 +63,7 @@ export const Layer = ( {
|
||||
|
||||
useMapEvents({
|
||||
popupopen: (e) => {
|
||||
const item = Object.entries(leafletRefs).find(r => r[1].popup == e.popup)?.[1].item;
|
||||
const item = Object.entries(leafletRefs).find(r => r[1].popup == e.popup)?.[1].item;
|
||||
if (item?.layer?.name == name && window.location.pathname.split("/")[2] != item.id) {
|
||||
window.history.pushState({}, "", `/${name}/${item.id}`)
|
||||
let title = "";
|
||||
@ -116,11 +115,6 @@ export const Layer = ( {
|
||||
filter(item => item.layer?.name === name)?.
|
||||
filter(item =>
|
||||
filterTags.length == 0 ? item : filterTags.every(tag => getItemTags(item).some(filterTag => filterTag.id === tag.id)))?.
|
||||
filter(item => {
|
||||
return searchPhrase === ''
|
||||
? item :
|
||||
item.name?.toLowerCase().includes(searchPhrase.toLowerCase()) || item.text?.toLowerCase().includes(searchPhrase.toLowerCase())
|
||||
}).
|
||||
filter(item => item.layer && isLayerVisible(item.layer)).
|
||||
map((item: Item) => {
|
||||
const tags = getItemTags(item);
|
||||
@ -139,7 +133,7 @@ export const Layer = ( {
|
||||
}
|
||||
return (
|
||||
<Marker ref={(r) => {
|
||||
if (!(item.id in leafletRefs))
|
||||
if (!(item.id in leafletRefs && leafletRefs[item.id].marker == r))
|
||||
r && addMarker(item, r);
|
||||
}} icon={MarkerIconFactory(markerShape, color1, color2, markerIcon)} key={item.id} position={[item.position.coordinates[1], item.position.coordinates[0]]}>
|
||||
{
|
||||
@ -147,7 +141,7 @@ export const Layer = ( {
|
||||
React.Children.toArray(children).map((child) =>
|
||||
React.isValidElement(child) && child.props.__TYPE === "ItemView" ?
|
||||
<ItemViewPopup ref={(r) => {
|
||||
if (!(item.id in leafletRefs))
|
||||
if (!(item.id in leafletRefs && leafletRefs[item.id].popup == r))
|
||||
r && addPopup(item, r as Popup);
|
||||
}} key={item.id + item.name}
|
||||
title={itemTitleField && item ? getValue(item, itemTitleField) : undefined}
|
||||
@ -162,7 +156,7 @@ export const Layer = ( {
|
||||
:
|
||||
<>
|
||||
<ItemViewPopup key={item.id + item.name} ref={(r) => {
|
||||
if (!(item.id in leafletRefs))
|
||||
if (!(item.id in leafletRefs && leafletRefs[item.id].popup == r))
|
||||
r && addPopup(item, r as Popup);
|
||||
}} title={itemTitleField && item ? getValue(item, itemTitleField) : undefined}
|
||||
avatar={itemAvatarField && item && getValue(item, itemAvatarField)? assetsApi.url + getValue(item, itemAvatarField) : undefined}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
import * as React from 'react'
|
||||
|
||||
export const Control = ({children}) => {
|
||||
return (
|
||||
<div className='tw-absolute tw-bottom-4 tw-left-4 tw-z-[999] tw-flex-col'>{children}</div>
|
||||
)
|
||||
}
|
||||
25
src/Components/Map/Subcomponents/Controls/Control.tsx
Normal file
25
src/Components/Map/Subcomponents/Controls/Control.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import * as L from 'leaflet'
|
||||
import * as React from 'react'
|
||||
|
||||
|
||||
|
||||
export const Control = ({ position, children }: { position: "topLeft" | "topRight" | "bottomLeft" | "bottomRight", children: React.ReactNode }) => {
|
||||
|
||||
|
||||
const controlContainerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
if (controlContainerRef.current !== null) {
|
||||
L.DomEvent.disableClickPropagation(controlContainerRef.current)
|
||||
L.DomEvent.disableScrollPropagation(controlContainerRef.current)
|
||||
}
|
||||
}, [controlContainerRef])
|
||||
|
||||
return (
|
||||
<div ref={controlContainerRef} className={`tw-absolute tw-z-[999] tw-flex-col ${position === 'topLeft' && "tw-top-4 tw-left-4"} ${position === 'bottomLeft' && "tw-bottom-4 tw-left-4"} ${position === 'topRight' && "tw-bottom-4 tw-right-4"} ${position === 'bottomRight' && "tw-bottom-4 tw-right-4"}`}>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import * as React from 'react'
|
||||
import * as L from 'leaflet'
|
||||
import { useLayers } from '../hooks/useLayers';
|
||||
import { useAddVisibleLayer, useIsLayerVisible, useToggleVisibleLayer } from '../hooks/useFilter';
|
||||
import { useLayers } from '../../hooks/useLayers';
|
||||
import { useAddVisibleLayer, useIsLayerVisible, useToggleVisibleLayer } from '../../hooks/useFilter';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function LayerControl() {
|
||||
@ -9,14 +9,6 @@ export function LayerControl() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const layers = useLayers();
|
||||
const controlContainerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (controlContainerRef.current !== null) {
|
||||
L.DomEvent.disableClickPropagation(controlContainerRef.current)
|
||||
L.DomEvent.disableScrollPropagation(controlContainerRef.current)
|
||||
}
|
||||
}, [controlContainerRef])
|
||||
|
||||
useEffect(() => {
|
||||
layers.map(layer =>
|
||||
@ -30,7 +22,7 @@ export function LayerControl() {
|
||||
const addVisibleLayer = useAddVisibleLayer();
|
||||
|
||||
return (
|
||||
<div ref={controlContainerRef} className="tw-card tw-bg-base-100 tw-shadow-xl " onClick={e => e.stopPropagation()}>
|
||||
<div className="tw-card tw-bg-base-100 tw-shadow-xl " onClick={e => e.stopPropagation()}>
|
||||
{
|
||||
open ?
|
||||
<div className="tw-card-body tw-p-2 tw-w-32 tw-transition-all tw-duration-300" onClick={e => e.stopPropagation()}>
|
||||
@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import { useQuestsOpen, useSetQuestOpen } from '../../Gaming/hooks/useQuests';
|
||||
import { useQuestsOpen, useSetQuestOpen } from '../../../Gaming/hooks/useQuests';
|
||||
|
||||
|
||||
export function QuestControl() {
|
||||
110
src/Components/Map/Subcomponents/Controls/SearchControl.tsx
Normal file
110
src/Components/Map/Subcomponents/Controls/SearchControl.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import * as React from 'react'
|
||||
import { useAddFilterTag, useSetSearchPhrase } from '../../hooks/useFilter'
|
||||
import useWindowDimensions from '../../hooks/useWindowDimension';
|
||||
import axios from 'axios';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useMap } from 'react-leaflet';
|
||||
import { LatLng, LatLngBounds } from 'leaflet';
|
||||
import { useDebounce } from '../../hooks/useDebounce';
|
||||
import { useTags } from '../../hooks/useTags';
|
||||
import { useItems } from '../../hooks/useItems';
|
||||
import { useLeafletRefs } from '../../hooks/useLeafletRefs';
|
||||
|
||||
|
||||
|
||||
export const SearchControl = ({ clusterRef }) => {
|
||||
|
||||
const windowDimensions = useWindowDimensions();
|
||||
const [value, setValue] = useState('');
|
||||
const [geoResults, setGeoResults] = useState<Array<any>>([]);
|
||||
const [tagsResults, setTagsResults] = useState<Array<any>>([]);
|
||||
const [itemsResults, setItemsResults] = useState<Array<any>>([]);
|
||||
const [hideSuggestions, setHideSuggestions] = useState(true);
|
||||
|
||||
const map = useMap();
|
||||
const tags = useTags();
|
||||
const items = useItems();
|
||||
const leafletRefs = useLeafletRefs();
|
||||
const addFilterTag = useAddFilterTag();
|
||||
|
||||
useDebounce(() => {
|
||||
const searchGeo = async () => {
|
||||
try {
|
||||
const { data } = await axios.get(
|
||||
`https://photon.komoot.io/api/?q=${value}&limit=5`
|
||||
);
|
||||
|
||||
setGeoResults(data.features);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
searchGeo();
|
||||
setItemsResults(items.filter(item => item.name?.toLowerCase().includes(value.toLowerCase()) || item.text?.toLowerCase().includes(value.toLowerCase())))
|
||||
setTagsResults(tags.filter(tag => tag.id?.toLowerCase().includes(value.toLowerCase())))
|
||||
|
||||
|
||||
|
||||
}, 500, [value]);
|
||||
|
||||
const searchInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
return (<>
|
||||
{!(windowDimensions.height < 500) &&
|
||||
<div className='tw-w-[calc(100vw-2rem)] tw-max-w-[22rem] '>
|
||||
<input type="text" placeholder="search ..." className="tw-input tw-input-bordered tw-w-full tw-shadow-xl tw-rounded-lg"
|
||||
ref={searchInput}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onFocus={() => setHideSuggestions(false)}
|
||||
onBlur={async () => {
|
||||
setTimeout(() => {
|
||||
setHideSuggestions(true);
|
||||
}, 200);
|
||||
}} />
|
||||
{hideSuggestions || Array.from(geoResults).length == 0 && itemsResults.length == 0 && tagsResults.length == 0 || value.length==0 ? "" :
|
||||
<div className='tw-card tw-card-body tw-bg-base-100 tw-p-4 tw-mt-2 tw-z-1000 tw-shadow-xl'>
|
||||
{tagsResults.length > 0 &&
|
||||
<div className='tw-flex tw-flex-wrap tw-max-h-16 tw-overflow-hidden'>
|
||||
{tagsResults.map(tag => (
|
||||
<div key={tag.id} className='tw-rounded-2xl tw-text-white tw-p-1 tw-px-4 tw-shadow-md tw-card tw-mr-2 tw-mb-2 tw-cursor-pointer' style={{ backgroundColor: tag.color }} onClick={() => {
|
||||
addFilterTag(tag)
|
||||
}}>
|
||||
<b>#{capitalizeFirstLetter(tag.id)}</b>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
|
||||
{itemsResults.length > 0 && tagsResults.length > 0 && <hr></hr>}
|
||||
{itemsResults.slice(0, 10).map(item => (
|
||||
<div key={item.id} className='tw-cursor-pointer hover:tw-font-bold' onClick={() => {
|
||||
const marker = Object.entries(leafletRefs).find(r => r[1].item == item)?.[1].marker;
|
||||
marker !== null && clusterRef?.current?.zoomToShowLayer(marker, () => {
|
||||
marker?.openPopup();
|
||||
});
|
||||
}
|
||||
}>{item.name}</div>
|
||||
))}
|
||||
{Array.from(geoResults).length > 0 && (itemsResults.length > 0 || tagsResults.length > 0) && <hr></hr>}
|
||||
{Array.from(geoResults).map((geo) => (
|
||||
<div className='tw-cursor-pointer hover:tw-font-bold' key={geo?.properties.osm_id}
|
||||
onClick={() => {
|
||||
searchInput.current?.blur();
|
||||
if (geo.properties.extent) map.fitBounds(new LatLngBounds(new LatLng(geo.properties.extent[1], geo.properties.extent[0]), new LatLng(geo.properties.extent[3], geo.properties.extent[2])));
|
||||
else map.setView(new LatLng(geo.geometry.coordinates[1], geo.geometry.coordinates[0]), 15, { duration: 1 })
|
||||
}}>
|
||||
{geo?.properties.name}
|
||||
</div>
|
||||
))}
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
28
src/Components/Map/Subcomponents/Controls/TagsControl.tsx
Normal file
28
src/Components/Map/Subcomponents/Controls/TagsControl.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import * as React from 'react'
|
||||
import { useFilterTags, useRemoveFilterTag } from '../../hooks/useFilter';
|
||||
|
||||
export const TagsControl = () => {
|
||||
|
||||
const filterTags = useFilterTags();
|
||||
const removeFilterTag = useRemoveFilterTag();
|
||||
|
||||
return (
|
||||
<div className='tw-flex tw-flex-wrap tw-mt-4 tw-w-[calc(100vw-2rem)] tw-max-w-xs'>
|
||||
{
|
||||
filterTags.map(tag =>
|
||||
<div key={tag.id} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mr-2 tw-mb-2' style={{ backgroundColor: tag.color }}>
|
||||
<div className="tw-card-actions tw-justify-end">
|
||||
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (removeFilterTag(tag.id))}>✕</label>
|
||||
</div><b>#{capitalizeFirstLetter(tag.id)}</b>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { useFilterTags, useRemoveFilterTag, useSetSearchPhrase } from '../hooks/useFilter'
|
||||
import useWindowDimensions from '../hooks/useWindowDimension';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
export const FilterControl = () => {
|
||||
|
||||
const windowDimensions = useWindowDimensions();
|
||||
/**
|
||||
const [popupOpen, setPopupOpen] = useState<boolean>(false);
|
||||
|
||||
useMapEvents({
|
||||
popupopen: (e) => {
|
||||
console.log(e);
|
||||
|
||||
setPopupOpen(true);
|
||||
},
|
||||
popupclose: () => {
|
||||
setPopupOpen(false);
|
||||
}
|
||||
})
|
||||
*/
|
||||
const filterTags = useFilterTags();
|
||||
const removeFilterTag = useRemoveFilterTag();
|
||||
const setSearchPhrase = useSetSearchPhrase();
|
||||
return (<>
|
||||
{!(
|
||||
//popupOpen &&
|
||||
windowDimensions.height < 500) &&
|
||||
<div className='tw-flex tw-flex-col tw-absolute tw-top-4 tw-left-4 tw-z-[699] tw-w-[calc(100vw-2rem)] tw-max-w-sm'>
|
||||
<input type="text" placeholder="search ..." className="tw-input tw-input-bordered tw-w-full tw-shadow-xl tw-rounded-lg" onChange={(e) => setSearchPhrase(e.target.value)} />
|
||||
<div className='tw-flex tw-flex-wrap tw-mt-4'>
|
||||
{
|
||||
filterTags.map(tag =>
|
||||
<div key={tag.id} className='tw-rounded-2xl tw-text-white tw-p-2 tw-px-4 tw-shadow-xl tw-card tw-mr-2 tw-mb-2' style={{ backgroundColor: tag.color }}>
|
||||
<div className="tw-card-actions tw-justify-end">
|
||||
<label className="tw-btn tw-btn-xs tw-btn-circle tw-absolute tw--right-2 tw--top-2 tw-bg-white tw-text-gray-600" onClick={() => (removeFilterTag(tag.id))}>✕</label>
|
||||
</div><b>#{capitalizeFirstLetter(tag.id)}</b>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
@ -12,13 +12,14 @@ import { ItemsProvider } from "./hooks/useItems";
|
||||
import { TagsProvider } from "./hooks/useTags";
|
||||
import { LayersProvider } from "./hooks/useLayers";
|
||||
import { FilterProvider } from "./hooks/useFilter";
|
||||
import { FilterControl } from "./Subcomponents/FilterControl";
|
||||
import { SearchControl } from "./Subcomponents/Controls/SearchControl";
|
||||
import { PermissionsProvider } from "./hooks/usePermissions";
|
||||
import { LeafletRefsProvider } from "./hooks/useLeafletRefs";
|
||||
import { LayerControl } from "./Subcomponents/LayerControl";
|
||||
import { QuestControl } from "./Subcomponents/QuestControl";
|
||||
import { Control } from "./Subcomponents/Control";
|
||||
import { LayerControl } from "./Subcomponents/Controls/LayerControl";
|
||||
import { QuestControl } from "./Subcomponents/Controls/QuestControl";
|
||||
import { Control } from "./Subcomponents/Controls/Control";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { TagsControl } from "./Subcomponents/Controls/TagsControl";
|
||||
|
||||
|
||||
export interface MapEventListenerProps {
|
||||
@ -57,10 +58,6 @@ function UtopiaMap({
|
||||
props.setSelectNewItemPosition(null)
|
||||
}
|
||||
},
|
||||
resize: () => {
|
||||
console.log("resize");
|
||||
},
|
||||
|
||||
})
|
||||
return null
|
||||
}
|
||||
@ -73,16 +70,19 @@ function UtopiaMap({
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayersProvider initialLayers={[]}>
|
||||
<TagsProvider initialTags={[]}>
|
||||
<PermissionsProvider initialPermissions={[]}>
|
||||
<FilterProvider initialTags={[]}>
|
||||
<ItemsProvider initialItems={[]}>
|
||||
<LeafletRefsProvider initialLeafletRefs={{}}>
|
||||
<LayersProvider initialLayers={[]}>
|
||||
<TagsProvider initialTags={[]}>
|
||||
<PermissionsProvider initialPermissions={[]}>
|
||||
<FilterProvider initialTags={[]}>
|
||||
<ItemsProvider initialItems={[]}>
|
||||
<LeafletRefsProvider initialLeafletRefs={{}}>
|
||||
<div className={(selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined)}>
|
||||
<MapContainer ref={mapDivRef} style={{ height: height, width: width }} center={center} zoom={zoom} zoomControl={false}>
|
||||
<FilterControl></FilterControl>
|
||||
<Control>
|
||||
<Control position='topLeft'>
|
||||
<SearchControl clusterRef={clusterRef}/>
|
||||
<TagsControl/>
|
||||
</Control>
|
||||
<Control position='bottomLeft'>
|
||||
<QuestControl></QuestControl>
|
||||
<LayerControl></LayerControl>
|
||||
</Control>
|
||||
@ -110,12 +110,12 @@ function UtopiaMap({
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</LeafletRefsProvider>
|
||||
</ItemsProvider>
|
||||
</FilterProvider>
|
||||
</PermissionsProvider>
|
||||
</TagsProvider>
|
||||
</LayersProvider>
|
||||
</LeafletRefsProvider>
|
||||
</ItemsProvider>
|
||||
</FilterProvider>
|
||||
</PermissionsProvider>
|
||||
</TagsProvider>
|
||||
</LayersProvider>
|
||||
<Outlet></Outlet>
|
||||
</>
|
||||
);
|
||||
|
||||
9
src/Components/Map/hooks/useDebounce.tsx
Normal file
9
src/Components/Map/hooks/useDebounce.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTimeout } from './useTimeout';
|
||||
|
||||
export const useDebounce = (callback, delay, deps) => {
|
||||
const { reset, clear } = useTimeout(callback, delay);
|
||||
|
||||
useEffect(reset, [...deps, reset]);
|
||||
useEffect(clear, []);
|
||||
}
|
||||
30
src/Components/Map/hooks/useTimeout.tsx
Normal file
30
src/Components/Map/hooks/useTimeout.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export const useTimeout = (callback, delay) => {
|
||||
const callbackRef = useRef(callback);
|
||||
const timeoutRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
const set = useCallback(() => {
|
||||
timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
|
||||
}, [delay]);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
timeoutRef.current && clearTimeout(timeoutRef.current);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
set();
|
||||
return clear;
|
||||
}, [delay, set, clear]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
clear();
|
||||
set();
|
||||
}, [clear, set]);
|
||||
|
||||
return { reset, clear };
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user