Merge branch 'maps/layer-selector-component' into 'main'
added layer selector in global maps See merge request vkuchenik/website-framework!69
This commit is contained in:
Generated
+10
@@ -14,6 +14,7 @@
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"framer-motion": "^12.23.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"lucide-react": "^0.539.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
@@ -3894,6 +3895,15 @@
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.539.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.539.0.tgz",
|
||||
"integrity": "sha512-VVISr+VF2krO91FeuCrm1rSOLACQUYVy7NQkzrOty52Y8TlTPcXcMdQFj9bYzBgXbWCiywlwSZ3Z8u6a+6bMlg==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"framer-motion": "^12.23.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"lucide-react": "^0.539.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useMap } from "react-leaflet";
|
||||
import L from "leaflet";
|
||||
import { useEffect, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
// props contain title of btn (which can be an icon), the onClick fn (what should happen)
|
||||
// when the settings btn is clicked, and it's positioning with the default being topright
|
||||
export default function MapButtonControls({settingsBtn, position = "topright"}) {
|
||||
const map = useMap(); // retrieves the map instance that is mounted
|
||||
const [container, setContainer] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// extending the leaflet control class
|
||||
const customControl = L.Control.extend({
|
||||
onAdd: () => {
|
||||
// create button element with className 'leaflet-bar'
|
||||
const dv = L.DomUtil.create("div", "layer-selector");
|
||||
dv.style.cursor = "pointer";
|
||||
|
||||
// prevent map interactions when clicking on settings btn
|
||||
L.DomEvent.disableClickPropagation(dv);
|
||||
// L.DomEvent.on(dv, "click", onClick); // pass your own onClick fn
|
||||
|
||||
// pass button to container for react to render
|
||||
setContainer(dv);
|
||||
return dv;
|
||||
},
|
||||
onRemove: () => {
|
||||
setContainer(null);
|
||||
}
|
||||
});
|
||||
|
||||
// instantiate new customControl object
|
||||
const control = new customControl({ position });
|
||||
// method inside og class that we extended above
|
||||
control.addTo(map);
|
||||
|
||||
return () => {
|
||||
control.remove();
|
||||
};
|
||||
}, [map, position, settingsBtn ]);
|
||||
|
||||
return container ? createPortal(
|
||||
settingsBtn,
|
||||
container)
|
||||
: null;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Box, Checkbox, Separator , CloseButton, Text, chakra, Field, IconButton, Icon, Input, Fieldset, Flex } from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { FiSettings } from "react-icons/fi";
|
||||
import { IoIosArrowDown } from "react-icons/io";
|
||||
import { IoIosArrowUp } from "react-icons/io";
|
||||
|
||||
export default function MapSettingsButton() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return open ? (
|
||||
<Box width="240px" display="flex" flexDir="column" gap="2" p="2" bg="white" borderRadius="5px" boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)">
|
||||
<Header {...{open, setOpen}}/>
|
||||
<Separator />
|
||||
<ThermalAnomalies />
|
||||
<Lightning />
|
||||
</Box>
|
||||
):(
|
||||
<IconButton boxShadow="0 0 0 2px rgba(119, 119, 119, 0.4)" size="sm" variant="subtle" onClick={() => setOpen(true)}>
|
||||
<FiSettings />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
function Header(props) {
|
||||
const { setOpen } = props;
|
||||
return (
|
||||
<chakra.div display="flex" dir="row" alignItems="center" justifyContent="space-between" pl="12">
|
||||
<Text textStyle="lg">Select Layers</Text>
|
||||
<CloseButton size="sm" colorPalette='red' onClick={() => setOpen(false)}/>
|
||||
</chakra.div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThermalAnomalies() {
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
return (
|
||||
<Box p="2" borderRadius="3px" bg={checked ? "blue.200":"blue.100"} color={checked ? "blue.700":"text"} border="1px solid" borderColor={checked ? "blue.600":"border"} >
|
||||
<Checkbox.Root colorPalette="blue">
|
||||
<Checkbox.HiddenInput onClick={() => setChecked(!checked)}/>
|
||||
<Checkbox.Control bg={checked ? undefined:"white"}/>
|
||||
<Checkbox.Label >Thermal Anomalies</Checkbox.Label>
|
||||
</Checkbox.Root>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function Lightning() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
return (
|
||||
<Box p="2" borderRadius="3px" bg={checked ? "blue.200":"blue.100"} color={checked ? "blue.700":"text"} border="1px solid" borderColor={checked ? "blue.600":"border"} >
|
||||
<Checkbox.Root colorPalette="blue">
|
||||
<Checkbox.HiddenInput onClick={() => {setChecked(!checked); console.log("I'm clicked!")}}/>
|
||||
<Checkbox.Control bg={checked ? undefined:"white"}/>
|
||||
<Checkbox.Label >Lightning</Checkbox.Label>
|
||||
</Checkbox.Root>
|
||||
<Icon pt="1" size="md" variant="none" onClick={() => setOpen(!open)}>
|
||||
{open ? (<IoIosArrowUp />):(<IoIosArrowDown />)}
|
||||
</Icon>
|
||||
{open && (<LightningOptions />)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function LightningOptions() {
|
||||
return (
|
||||
<Fieldset.Root size="xs">
|
||||
<Field.Root pl="10" gap={0} >
|
||||
<Field.Label>Radius (km)</Field.Label>
|
||||
<Input height="1.25rem" width="4rem" bg="white" border="none" borderRadius="6px" fontStyle="italic" placeholder="max"/>
|
||||
</Field.Root>
|
||||
<Field.Root pl="10" gap={0} >
|
||||
<Field.Label>Date Range</Field.Label>
|
||||
<Flex dir="row" gap="1">
|
||||
<Input height="1.25rem" width="4rem" bg="white" border="none" borderRadius="6px" fontStyle="italic" placeholder="min"/>
|
||||
<Text fontStyle="italic">to</Text>
|
||||
<Input height="1.25rem" width="4rem" bg="white" border="none" borderRadius="6px" fontStyle="italic" placeholder="max"/>
|
||||
</Flex>
|
||||
</Field.Root>
|
||||
</Fieldset.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
import React from "react";
|
||||
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";
|
||||
import MapButtonControls from "../CanvasComponents/MapButtonControls";
|
||||
import MapSettingsButton from "../CanvasComponents/MapSettingsButton";
|
||||
|
||||
export default function GlobalMap() {
|
||||
const position = [45.61142478054226, -122.49630033167544]; // [latitude, longitude]
|
||||
|
||||
return (
|
||||
<MapContainer center={position} zoom={3} style={{ height: "100%", width: "100%" }}>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution='© OpenStreetMap contributors'
|
||||
/>
|
||||
<MapButtonControls settingsBtn = {<MapSettingsButton />} position="topright" />
|
||||
<Marker position={position}>
|
||||
<Popup>
|
||||
CVO — Home of the volcanoligists
|
||||
|
||||
@@ -11,6 +11,7 @@ import { MdOutlineSecurity } from "react-icons/md";
|
||||
import { IoPersonCircleSharp } from "react-icons/io5";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { DASHBOARD_VIEWS } from "@/constants/viewKeys";
|
||||
import { KeyRound } from 'lucide-react';
|
||||
|
||||
export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
@@ -23,7 +24,7 @@ export default function Sidebar({ view, onChangeView, darkMode, setDarkMode }) {
|
||||
if (["settings", "personal", "security"].includes(view)) {
|
||||
return [
|
||||
{icon: IoPersonCircleSharp, name: "Personal Info", view: "personal"},
|
||||
{icon: MdOutlineSecurity, name: "Security", view: "security"},
|
||||
{icon: KeyRound, name: "Security", view: "security"},
|
||||
];
|
||||
}
|
||||
return [
|
||||
|
||||
+11
-1
@@ -149,10 +149,20 @@ const config = defineConfig({
|
||||
}
|
||||
},
|
||||
'.leaflet-container': {
|
||||
fontFamily: "georgia, system-ui, sans-serif !important",
|
||||
_dark: {
|
||||
filter: "invert(100%) hue-rotate(180deg) brightness(95%) contrast(90%)",
|
||||
}
|
||||
},
|
||||
".leaflet-popup-content": {
|
||||
fontFamily: "georgia, system-ui, sans-serif !important"
|
||||
},
|
||||
".leaflet-control a": {
|
||||
fontFamily: "georgia, system-ui, sans-serif !important"
|
||||
},
|
||||
".leaflet-tooltip": {
|
||||
fontFamily: "georgia, system-ui, sans-serif !important"
|
||||
},
|
||||
".react-grid-item": {
|
||||
transition: "none !important"
|
||||
},
|
||||
@@ -166,7 +176,7 @@ const config = defineConfig({
|
||||
primary: { value: {base: "#2B98F8", _dark: "#2B98F8"}},
|
||||
primaryLt: { value: {base: "#98cdffff", _dark: "#98cdffff"}},
|
||||
secondaryBG: { value: {base: "#EDF1F1", _dark: "#121212"}},
|
||||
border: {value: {base: "#000000", _dark: "#CBCBCB"}},
|
||||
border: {value: {base: "#000000", _dark: "#cbcbcb84"}},
|
||||
text: {value: {base: "#000000", _dark: "lightgrey"}},
|
||||
textInverted: {value: "white"},
|
||||
icon: {value: {base: "#000000", _dark: "lightgrey"}},
|
||||
|
||||
Reference in New Issue
Block a user