Skip to content

Commit

Permalink
feat(map): add searchbox
Browse files Browse the repository at this point in the history
  • Loading branch information
nimdanitro committed Dec 4, 2024
1 parent 8eb03cf commit cd5dfb9
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 1 deletion.
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"react-autocomplete-hint": "^2.0.0",
"react-color": "^2.19.3",
"react-dom": "^18.3.1",
"react-html-parser": "^2.0.2",
"react-i18next": "^15.1.3",
"react-map-gl": "~7.1.7",
"react-markdown": "^9.0.1",
Expand Down Expand Up @@ -84,6 +85,7 @@
"@types/react": "^18.3.12",
"@types/react-color": "^3.0.12",
"@types/react-dom": "^18.3.1",
"@types/react-html-parser": "^2",
"@types/react-router-dom": "^5.3.3",
"@types/semver": "^7.5.8",
"@types/uuid": "^10.0.0",
Expand Down
2 changes: 2 additions & 0 deletions ui/src/views/map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import LayerControl from "./controls/LayerControl";
import MapboxDraw from "@mapbox/mapbox-gl-draw";
import maplibregl from "maplibre-gl";
import classNames from "classnames";
import SearchControl from "./controls/Searchbox";

const modes = {
...MapboxDraw.modes,
Expand Down Expand Up @@ -77,6 +78,7 @@ function MapView() {
reuseMaps={false}
RTLTextPlugin={undefined}
>
<SearchControl />
<AttributionControl position="bottom-left" compact={true} />
{/* All Map Controls */}
<FullscreenControl position={"top-left"} />
Expand Down
129 changes: 129 additions & 0 deletions ui/src/views/map/controls/Searchbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { useState, useCallback } from "react";
import classNames from "classnames";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSearch } from "@fortawesome/free-solid-svg-icons";
import { useMap } from "react-map-gl/maplibre";
import debounce from "lodash/debounce";
import isEmpty from "lodash/isEmpty";
import ReactHtmlParser from "react-html-parser";

const BASE_URL = "https://api3.geo.admin.ch/rest/services/api/SearchServer";

interface SearchResult {
bbox: number[];
features: SearchFeature[];
}

interface SearchFeature {
bbox: number[];
geometry: {
coordinates: number[];
type: string;
};
id: number | string;
properties: {
detail: string;
label: string;
rank: number;
type: string;
geom_quadindex: string;
lat: number;
lon: number;
objectclass: string;
origin: string;
weight: number;
x: number;
y: number;
zoomlevel: number;
};
}

function SearchControl() {
const { current: map } = useMap();
const [searchResults, setSearchResults] = useState<SearchFeature[]>([]);
const [input, setInput] = useState<string>("");

const flyTo = useCallback(
(target: SearchFeature) => {
map?.flyTo({
center: [target.properties.lon, target.properties.lat],
zoom: 17,
animate: true,
duration: 2500,
});
setSearchResults([]);
setInput("");
},
[map],
);

const search = (input: string) => {
fetch(
BASE_URL +
"?" +
new URLSearchParams({
searchText: input,
type: "locations",
geometryFormat: "geojson",
origins: "address,gazetteer,parcel",
limit: "10",
}),
)
.then((response) => response.json())
.then((data) => {
const searchResult: SearchResult = {
bbox: data.bbox,
features: data.features,
};
setSearchResults(searchResult.features);
});
};
const debouncedSearch = debounce(search, 1000);

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInput(value);
debouncedSearch(value);
};

const dropdown = classNames({
dropdown: true,
"is-active": !isEmpty(searchResults),
});

return (
<div className="is-flex is-justify-content-center is-align-content-center mt-3">
<div className={dropdown}>
<div className="dropdown-trigger">
<div className="field has-addons">
<div className="control is-expanded has-icons-left">
<span className="icon is-left">
<FontAwesomeIcon icon={faSearch} />
</span>
<input
className="input"
type="search"
value={input}
placeholder=""
onChange={onChange}
onKeyDown={(e) => e.key === "Enter" && search(input)}
/>
</div>
</div>
<div className="dropdown-menu" id="dropdown-menu">
<div className="dropdown-content">
{searchResults &&
searchResults.map((result: SearchFeature) => (
<a onClick={() => flyTo(result)} key={result.id} className="dropdown-item">
{ReactHtmlParser(result.properties.label)}
</a>
))}
</div>
</div>
</div>
</div>
</div>
);
}

export default SearchControl;
135 changes: 134 additions & 1 deletion ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4328,6 +4328,22 @@ __metadata:
languageName: node
linkType: hard

"@types/domhandler@npm:^2.4.0, @types/domhandler@npm:^2.4.3":
version: 2.4.5
resolution: "@types/domhandler@npm:2.4.5"
checksum: 10/0dd551361c0ec4e8736a6bcb142ca3c9eedd9b8076cded263c555c0e4adef9b07230dc85b7c94e5cd72e9796ea2533c00e4bcf97d5e06bc10e32367429985ff1
languageName: node
linkType: hard

"@types/domutils@npm:*":
version: 1.7.8
resolution: "@types/domutils@npm:1.7.8"
dependencies:
"@types/domhandler": "npm:^2.4.0"
checksum: 10/ac97a3c2baf6c7ec93a3e54a30e36363dc4acaaf76b36e8b7dc5a06d626f48ce1d4beb9cd9767c1ce745825b78296528b60a128627c324c66eaec6616dc2ea42
languageName: node
linkType: hard

"@types/eslint-config-prettier@npm:^6.11.3":
version: 6.11.3
resolution: "@types/eslint-config-prettier@npm:6.11.3"
Expand Down Expand Up @@ -4456,6 +4472,18 @@ __metadata:
languageName: node
linkType: hard

"@types/htmlparser2@npm:*":
version: 3.10.7
resolution: "@types/htmlparser2@npm:3.10.7"
dependencies:
"@types/domhandler": "npm:^2.4.3"
"@types/domutils": "npm:*"
"@types/node": "npm:*"
domhandler: "npm:^2.4.0"
checksum: 10/74fbd402f554ac529cc7ff1aedbdc05eabd13694a636ae91cf5842c7fe8d1a0c4253c9400b957203f01d8564615771296d22f85132be894f71eb4254dd81adb6
languageName: node
linkType: hard

"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1":
version: 2.0.4
resolution: "@types/istanbul-lib-coverage@npm:2.0.4"
Expand Down Expand Up @@ -4644,6 +4672,16 @@ __metadata:
languageName: node
linkType: hard

"@types/react-html-parser@npm:^2":
version: 2.0.6
resolution: "@types/react-html-parser@npm:2.0.6"
dependencies:
"@types/htmlparser2": "npm:*"
"@types/react": "npm:*"
checksum: 10/6c7568c55313be6e79370ac82bf35d78925d81535492716be50ddd7eb8c1a9f9c25223bc731843cb679155b74a53111d2136dbd0aac31ccb83a43167f46d53d6
languageName: node
linkType: hard

"@types/react-router-dom@npm:^5.3.3":
version: 5.3.3
resolution: "@types/react-router-dom@npm:5.3.3"
Expand Down Expand Up @@ -6674,13 +6712,56 @@ __metadata:
languageName: node
linkType: hard

"dom-serializer@npm:0":
version: 0.2.2
resolution: "dom-serializer@npm:0.2.2"
dependencies:
domelementtype: "npm:^2.0.1"
entities: "npm:^2.0.0"
checksum: 10/376344893e4feccab649a14ca1a46473e9961f40fe62479ea692d4fee4d9df1c00ca8654811a79c1ca7b020096987e1ca4fb4d7f8bae32c1db800a680a0e5d5e
languageName: node
linkType: hard

"domelementtype@npm:1, domelementtype@npm:^1.3.1":
version: 1.3.1
resolution: "domelementtype@npm:1.3.1"
checksum: 10/7893da40218ae2106ec6ffc146b17f203487a52f5228b032ea7aa470e41dfe03e1bd762d0ee0139e792195efda765434b04b43cddcf63207b098f6ae44b36ad6
languageName: node
linkType: hard

"domelementtype@npm:^2.0.1":
version: 2.3.0
resolution: "domelementtype@npm:2.3.0"
checksum: 10/ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6
languageName: node
linkType: hard

"domhandler@npm:^2.3.0, domhandler@npm:^2.4.0":
version: 2.4.2
resolution: "domhandler@npm:2.4.2"
dependencies:
domelementtype: "npm:1"
checksum: 10/d8b0303c53c0eda912e45820ef8f6023f8462a724e8b824324f27923970222a250c7569e067de398c4d9ca3ce0f2b2d2818bc632d6fa72956721d6729479a9b9
languageName: node
linkType: hard

"dompurify@npm:^2.5.4":
version: 2.5.7
resolution: "dompurify@npm:2.5.7"
checksum: 10/b150ca1e28083252cd51097162dc96cb45203f7e2af1fbaa8ef32b4f4d6b605e4aa8915190d38bd0635cbbf14d13a200138cd3ec1b084096819b14c718355122
languageName: node
linkType: hard

"domutils@npm:^1.5.1":
version: 1.7.0
resolution: "domutils@npm:1.7.0"
dependencies:
dom-serializer: "npm:0"
domelementtype: "npm:1"
checksum: 10/8c1d879fd3bbfc0156c970d12ebdf530f541cbda895d7f631b2444d22bbb9d0e5a3a4c3210cffb17708ad67531d7d40e1bef95e915c53a218d268607b66b63c8
languageName: node
linkType: hard

"dot-case@npm:^3.0.4":
version: 3.0.4
resolution: "dot-case@npm:3.0.4"
Expand Down Expand Up @@ -6781,6 +6862,20 @@ __metadata:
languageName: node
linkType: hard

"entities@npm:^1.1.1":
version: 1.1.2
resolution: "entities@npm:1.1.2"
checksum: 10/4a707022f4e932060f03df2526be55d085a2576fe534421e5b22bc62abb0d1f04241c171f9981e3d7baa4f4160606cad72a2f7eb01b6a25e279e3f31a2be4bf2
languageName: node
linkType: hard

"entities@npm:^2.0.0":
version: 2.2.0
resolution: "entities@npm:2.2.0"
checksum: 10/2c765221ee324dbe25e1b8ca5d1bf2a4d39e750548f2e85cbf7ca1d167d709689ddf1796623e66666ae747364c11ed512c03b48c5bbe70968d30f2a4009509b7
languageName: node
linkType: hard

"entities@npm:^4.4.0":
version: 4.4.0
resolution: "entities@npm:4.4.0"
Expand Down Expand Up @@ -8434,6 +8529,20 @@ __metadata:
languageName: node
linkType: hard

"htmlparser2@npm:^3.9.0":
version: 3.10.1
resolution: "htmlparser2@npm:3.10.1"
dependencies:
domelementtype: "npm:^1.3.1"
domhandler: "npm:^2.3.0"
domutils: "npm:^1.5.1"
entities: "npm:^1.1.1"
inherits: "npm:^2.0.1"
readable-stream: "npm:^3.1.1"
checksum: 10/d5297fe76c0d6b0f35f39781417eb560ef12fa121953578083f3f2b240c74d5c35a38185689d181b6a82b66a3025436f14aa3413b94f3cd50ba15733f2f72389
languageName: node
linkType: hard

"http-cache-semantics@npm:^4.1.0":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
Expand Down Expand Up @@ -8641,7 +8750,7 @@ __metadata:
languageName: node
linkType: hard

"inherits@npm:2, inherits@npm:^2.0.3":
"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521
Expand Down Expand Up @@ -11951,6 +12060,17 @@ __metadata:
languageName: node
linkType: hard

"react-html-parser@npm:^2.0.2":
version: 2.0.2
resolution: "react-html-parser@npm:2.0.2"
dependencies:
htmlparser2: "npm:^3.9.0"
peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16.0.0-0
checksum: 10/b4d9c5faf5c0add929ef7364b451db4ac9b9b10dd0f671f7316ce4486e42a45f674e59183effa1b08c3fb3ddb70f6dbdc07c1f6d882aeb65b54c586dfe205b9f
languageName: node
linkType: hard

"react-i18next@npm:^15.1.3":
version: 15.1.3
resolution: "react-i18next@npm:15.1.3"
Expand Down Expand Up @@ -12073,6 +12193,17 @@ __metadata:
languageName: node
linkType: hard

"readable-stream@npm:^3.1.1":
version: 3.6.2
resolution: "readable-stream@npm:3.6.2"
dependencies:
inherits: "npm:^2.0.3"
string_decoder: "npm:^1.1.1"
util-deprecate: "npm:^1.0.1"
checksum: 10/d9e3e53193adcdb79d8f10f2a1f6989bd4389f5936c6f8b870e77570853561c362bee69feca2bbb7b32368ce96a85504aa4cedf7cf80f36e6a9de30d64244048
languageName: node
linkType: hard

"readable-stream@npm:^3.6.0":
version: 3.6.0
resolution: "readable-stream@npm:3.6.0"
Expand Down Expand Up @@ -13004,6 +13135,7 @@ __metadata:
"@types/react": "npm:^18.3.12"
"@types/react-color": "npm:^3.0.12"
"@types/react-dom": "npm:^18.3.1"
"@types/react-html-parser": "npm:^2"
"@types/react-router-dom": "npm:^5.3.3"
"@types/semver": "npm:^7.5.8"
"@types/uuid": "npm:^10.0.0"
Expand Down Expand Up @@ -13038,6 +13170,7 @@ __metadata:
react-autocomplete-hint: "npm:^2.0.0"
react-color: "npm:^2.19.3"
react-dom: "npm:^18.3.1"
react-html-parser: "npm:^2.0.2"
react-i18next: "npm:^15.1.3"
react-map-gl: "npm:~7.1.7"
react-markdown: "npm:^9.0.1"
Expand Down

0 comments on commit cd5dfb9

Please sign in to comment.