This is an automated email from the ASF dual-hosted git repository.

bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps-site.git


The following commit(s) were added to refs/heads/main by this push:
     new 2053e74  Add react components to the demo (#23)
2053e74 is described below

commit 2053e748d99c146c625514d5a0821584a958a22a
Author: Leonard <[email protected]>
AuthorDate: Thu May 4 12:47:58 2023 +0200

    Add react components to the demo (#23)
    
    * Add geocoder search component
    
    * Add iptoloc
    
    * Add new icon title component
    
    * Add info on geocoder
    
    * Update to https
    
    * Remove unused code
---
 src/components/GeocoderSearch/index.tsx          | 130 +++++++++++++++++++++++
 src/components/GeocoderSearch/style.module.css   | 107 +++++++++++++++++++
 src/components/map/index.tsx                     | 102 ++++++++++++------
 src/components/titles/IconTitle/index.tsx        |  21 ++++
 src/components/titles/IconTitle/style.module.css |   7 ++
 src/pages/_app.mdx                               |   2 +
 src/pages/index.mdx                              |  36 +++++--
 7 files changed, 366 insertions(+), 39 deletions(-)

diff --git a/src/components/GeocoderSearch/index.tsx 
b/src/components/GeocoderSearch/index.tsx
new file mode 100644
index 0000000..547e525
--- /dev/null
+++ b/src/components/GeocoderSearch/index.tsx
@@ -0,0 +1,130 @@
+import { useCallback, useState } from 'react';
+import maplibregl from 'maplibre-gl';
+import cn from 'clsx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSearch } from '@fortawesome/free-solid-svg-icons';
+
+import styles from './style.module.css';
+
+type GeocoderResults = {
+  data: {
+    name: string;
+    latitude: number;
+    longitude: number;
+  } & Record<string, any>;
+}[];
+
+interface InputSearchProps {
+  url: string;
+  map?: maplibregl.Map;
+}
+
+export const GeocoderSearch: React.FC<InputSearchProps> = ({ url, map }) => {
+  const [search, setSearch] = useState('');
+  const [results, setResults] = useState<GeocoderResults>([]);
+  const [marker, setMarker] = useState<maplibregl.Marker | null>(null);
+  const [error, setError] = useState(false);
+
+  const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
+    setSearch(e.target.value);
+    if (e.target.value.length < 3) {
+      setResults([]);
+      setError(false);
+      return;
+    }
+    try {
+      const res = await fetch(
+        url + '?' + new URLSearchParams({ queryText: e.target.value }),
+        { method: 'GET' }
+      );
+      const data = await res.json();
+      setResults(data.results.slice(0, 10) as GeocoderResults);
+      setError(false);
+    } catch (err) {
+      setError(true);
+    }
+  };
+
+  const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
+    e.preventDefault();
+    if (results.length > 0) {
+      const first = results[0].data;
+      updateMap(first);
+    }
+  };
+
+  const updateMap = useCallback(
+    (result: GeocoderResults[0]['data']) => {
+      if (!map) {
+        return;
+      }
+      if (marker) {
+        marker.remove();
+      }
+      const newMarker = new maplibregl.Marker()
+        .setLngLat([result.longitude, result.latitude])
+        .addTo(map);
+      setMarker(newMarker);
+      map.panTo([result.longitude, result.latitude]);
+      setResults([]);
+      setSearch(result.name);
+    },
+    [map, marker]
+  );
+
+  return (
+    <div className={cn(styles['ctrl-container'], 'maplibregl-ctrl-top-left')}>
+      <div
+        className={cn(
+          styles['ctrl-group'],
+          'maplibregl-ctrl maplibregl-ctrl-group'
+        )}
+      >
+        <FontAwesomeIcon icon={faSearch} className={styles['search-icon']} />
+        <form onSubmit={onSubmit} className={styles['search-form']}>
+          <input
+            className={cn(styles.search)}
+            type="text"
+            placeholder="Search"
+            value={search}
+            onChange={e => onChange(e)}
+          />
+        </form>
+      </div>
+      {(results.length > 0 || error) && (
+        <div
+          className={cn(
+            styles['ctrl-group-dropdown'],
+            'maplibregl-ctrl maplibregl-ctrl-group'
+          )}
+        >
+          {error ? (
+            <div className={styles['search-error']}>
+              An error occured while searching...
+            </div>
+          ) : (
+            <>
+              <ul className={styles['search-results']}>
+                {results.map((result, index) => (
+                  <li
+                    key={index}
+                    className={styles['search-result']}
+                    onClick={() => updateMap(result.data)}
+                  >
+                    <div className={styles['search-result-name']}>
+                      {result.data.name}
+                    </div>
+                    <div className={styles['search-result-country']}>
+                      {result.data.countryCode}
+                    </div>
+                  </li>
+                ))}
+              </ul>
+              <small className={styles['attribution']}>© GeoNames</small>
+            </>
+          )}
+        </div>
+      )}
+    </div>
+  );
+};
diff --git a/src/components/GeocoderSearch/style.module.css 
b/src/components/GeocoderSearch/style.module.css
new file mode 100644
index 0000000..a519823
--- /dev/null
+++ b/src/components/GeocoderSearch/style.module.css
@@ -0,0 +1,107 @@
+.ctrl-container {
+  width: 450px;
+  color: black;
+}
+
+/* Responsive width for small screens */
+@media screen and (max-width: 768px) {
+  .ctrl-container {
+    width: 85%;
+  }
+}
+
+.ctrl-group {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  transition: box-shadow 0.1s ease-in-out;
+}
+
+.ctrl-group:has(.search:focus-visible) {
+  box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.25);
+}
+
+.ctrl-group-dropdown {
+  overflow: hidden;
+  width: 100%;
+  transition: width 0.1s ease-in-out;
+}
+
+.search-form {
+  width: 100%;
+}
+
+.search {
+  /* Place the search at the top */
+  z-index: 1;
+  width: 100%;
+  height: 29px;
+  border: 0;
+  display: block;
+  outline: none;
+  padding: 0 10px;
+  background-color: transparent;
+}
+
+.search:focus-visible {
+  box-shadow: none;
+}
+
+/* Center vertically search text placeholder */
+.search::-webkit-input-placeholder {
+  line-height: 29px;
+}
+
+.search-icon {
+  color: #233333;
+  fill: #233333;
+  padding-left: 10px;
+}
+
+.search-results {
+  width: 100%;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  gap: 5px;
+}
+
+.search-result {
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+  transition: background-color 0.1s ease-in-out;
+}
+
+.search-result:first-of-type {
+  padding-top: 5px;
+}
+
+.search-result:hover {
+  background-color: #e6e6e6;
+}
+
+.search-result-country {
+  margin-left: auto;
+  padding: 2px 5px;
+  border-radius: 4px;
+  background-color: #e6e6e6;
+  font-size: 12px;
+  font-weight: 700;
+}
+
+.search-error {
+  width: 100%;
+  color: gray;
+  font-size: 12px;
+  padding: 5px 10px;
+}
+
+.attribution {
+  width: 100%;
+  display: block;
+  margin: 5px auto;
+  color: gray;
+  text-align: center;
+  font-size: 12px;
+}
diff --git a/src/components/map/index.tsx b/src/components/map/index.tsx
index 65d6421..07ef0b5 100644
--- a/src/components/map/index.tsx
+++ b/src/components/map/index.tsx
@@ -4,47 +4,85 @@ import MaplibreInspect from 
'@/lib/maplibre/dist/maplibre-gl-inspect/maplibre-gl
 import MaplibreTileBoundaries from 
'@/lib/maplibre/dist/maplibre-gl-tile-boundaries/maplibre-gl-tile-boundaries';
 
 import styles from './style.module.css';
+import { GeocoderSearch } from '../GeocoderSearch';
 
-export default function Map() {
+interface MapProps {
+  longitude?: number;
+  latitude?: number;
+  zoom?: number;
+  ipToLoc?: boolean;
+}
+
+export default function Map({
+  longitude = 6.625,
+  latitude = 46.51,
+  zoom = 14,
+  ipToLoc = true
+}: MapProps) {
   const mapContainer = useRef(null);
-  const map = useRef(null);
-  const [lng] = useState(6.625);
-  const [lat] = useState(46.51);
-  const [zoom] = useState(14);
+  const [map, setMap] = useState(null);
 
+  // Initialize map when component mounts
   useEffect(() => {
-    if (map.current) return;
-    map.current = new maplibregl.Map({
-      container: mapContainer.current,
-      style: `https://demo.baremaps.com/style.json`,
-      center: [lng, lat],
-      zoom: zoom
-    });
-    map.current.addControl(new maplibregl.NavigationControl({}));
-
-    // Add the inspect control to the map.
-    map.current.addControl(
-      new MaplibreInspect({
-        showMapPopup: true,
-        showMapPopupOnHover: false,
-        showInspectMapPopupOnHover: false,
-        popup: new maplibregl.Popup({
-          closeButton: true,
-          closeOnClick: true
+    const initMap = async () => {
+      if (ipToLoc) {
+        try {
+          const res = await fetch('https://api.ipify.org/?format=json');
+          const { ip } = await res.json();
+          const res2 = await fetch(`https://demo.baremaps.com/api/ip/${ip}`);
+          const results = await res2.json();
+          if (results.length > 0) {
+            longitude = results[0].longitude;
+            latitude = results[0].latitude;
+          }
+          map.jumpTo({
+            center: [longitude, latitude],
+            zoom: zoom
+          });
+        } catch (err) {
+          // For the moment, we fallback to the default values
+        }
+      }
+      setMap(
+        new maplibregl.Map({
+          container: mapContainer.current,
+          style: `https://demo.baremaps.com/style.json`,
+          center: [longitude, latitude],
+          zoom: zoom
         })
-      })
-    );
+      );
+    };
+    initMap();
+  }, []);
 
-    // Add the tile boundaries control to the map.
-    map.current.addControl(
-      new MaplibreTileBoundaries({
-        showBoundaries: false
-      })
-    );
-  });
+  // Add controls to the map
+  useEffect(() => {
+    if (map) {
+      map.addControl(new maplibregl.NavigationControl({}));
+      // Add the inspect control to the map.
+      map.addControl(
+        new MaplibreInspect({
+          showMapPopup: true,
+          showMapPopupOnHover: false,
+          showInspectMapPopupOnHover: false,
+          popup: new maplibregl.Popup({
+            closeButton: true,
+            closeOnClick: true
+          })
+        })
+      );
+      // Add the tile boundaries control to the map.
+      map.addControl(
+        new MaplibreTileBoundaries({
+          showBoundaries: false
+        })
+      );
+    }
+  }, [map]);
 
   return (
     <div className={styles.wrap}>
+      <GeocoderSearch url="https://demo.baremaps.com/api/geocoder"; map={map} />
       <div ref={mapContainer} className={styles.map} />
     </div>
   );
diff --git a/src/components/titles/IconTitle/index.tsx 
b/src/components/titles/IconTitle/index.tsx
new file mode 100644
index 0000000..378b4e1
--- /dev/null
+++ b/src/components/titles/IconTitle/index.tsx
@@ -0,0 +1,21 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import styles from './style.module.css';
+import { type IconDefinition } from '@fortawesome/fontawesome-svg-core';
+
+interface IconTitleProps extends React.HTMLAttributes<HTMLDivElement> {
+  children: React.ReactNode;
+  icon: IconDefinition;
+}
+
+export const IconTitle: React.FC<IconTitleProps> = ({
+  children,
+  icon,
+  ...props
+}) => {
+  return (
+    <div className={styles['title-container']} {...props}>
+      <FontAwesomeIcon icon={icon} fontSize={40} />
+      {children}
+    </div>
+  );
+};
diff --git a/src/components/titles/IconTitle/style.module.css 
b/src/components/titles/IconTitle/style.module.css
new file mode 100644
index 0000000..766aeec
--- /dev/null
+++ b/src/components/titles/IconTitle/style.module.css
@@ -0,0 +1,7 @@
+.title-container {
+  display: flex;
+  align-items: center;
+  justify-content: start;
+  gap: 1.5rem;
+  margin-bottom: 0.75rem;
+}
diff --git a/src/pages/_app.mdx b/src/pages/_app.mdx
index 215d7b7..f2d7a40 100644
--- a/src/pages/_app.mdx
+++ b/src/pages/_app.mdx
@@ -2,6 +2,8 @@ import 'maplibre-gl/dist/maplibre-gl.css';
 import '@/lib/maplibre/dist/maplibre-gl-inspect/maplibre-gl-inspect.css';
 import 
'@/lib/maplibre/dist/maplibre-gl-tile-boundaries/maplibre-gl-tile-boundaries.css';
 
+import '@fortawesome/fontawesome-svg-core/styles.css';
+
 export default function App({ Component, pageProps }) {
   return <Component {...pageProps} />;
 }
diff --git a/src/pages/index.mdx b/src/pages/index.mdx
index 59c90be..e4b0f2b 100644
--- a/src/pages/index.mdx
+++ b/src/pages/index.mdx
@@ -5,11 +5,15 @@ title: Mapping Infrastructure Made Easy
 import Link from 'next/link';
 import Image from 'next/image';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faMap } from '@fortawesome/free-regular-svg-icons';
+import {
+  faListCheck,
+  faVectorSquare,
+  faEarthAmericas,
+  faPeopleGroup
+} from '@fortawesome/free-solid-svg-icons';
 import Map from '@/components/map';
 import { Features, Feature } from '@/components/features';
-
-import styles from './index.module.css';
+import { IconTitle } from '@/components/titles/IconTitle';
 
 <div className="home-content">
   <div className="content-container">
@@ -21,7 +25,7 @@ import styles from './index.module.css';
       online maps.
     </div>
     <div className="subtitle">
-      <Link className={styles.cta} href="/documentation">
+      <Link href="/documentation">
         Get started <span>→</span>
       </Link>
     </div>
@@ -33,7 +37,9 @@ import styles from './index.module.css';
           <Map />
         </Feature>
         <Feature index={0}>
-          <h3>Data Pipeline</h3>
+          <IconTitle icon={faListCheck}>
+            <h3>Data Pipeline</h3>
+          </IconTitle>
           <div>
             With Baremaps, you can easily create custom data pipelines to
             consolidate your spatial datasets in PostGIS. Baremaps supports
@@ -43,7 +49,9 @@ import styles from './index.module.css';
           </div>
         </Feature>
         <Feature index={0}>
-          <h3>Vector Tiles</h3>
+          <IconTitle icon={faVectorSquare}>
+            <h3>Vector Tiles</h3>
+          </IconTitle>
           <div>
             Baremaps allows you to easily serve and publish custom vector tiles
             from PostGIS. Whether you need to create maps for web or mobile
@@ -52,7 +60,21 @@ import styles from './index.module.css';
           </div>
         </Feature>
         <Feature index={0}>
-          <h3>Contributing</h3>
+          <IconTitle icon={faEarthAmericas}>
+            <h3>Geocoder</h3>
+          </IconTitle>
+          <div>
+            Baremaps provides a geocoder that allows you to search for places
+            and addresses. The geocoder is based on the GeoNames database and
+            can be easily integrated into your existing applications. We are
+            working on adding more features and improving the quality of the
+            results.
+          </div>
+        </Feature>
+        <Feature index={0}>
+          <IconTitle icon={faPeopleGroup}>
+            <h3>Contributing</h3>
+          </IconTitle>
           <div>
             Whether you're an experienced or just getting started, there are
             many ways to get involved. We are experimenting with a range of new

Reply via email to