diff --git a/.env.example b/.env.example index 2259bc556..c6a12b3e6 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,4 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" SESSION_SECRET="super-duper-s3cret" +MAPTILER_KEY="PUT_YOUR_KEY_HERE" +OSEM_API_URL="https://api.opensensemap.org/" diff --git a/app/components/Map.tsx b/app/components/Map.tsx new file mode 100644 index 000000000..073b20353 --- /dev/null +++ b/app/components/Map.tsx @@ -0,0 +1,62 @@ +import React, { useRef, useEffect, useState } from "react"; +import type { Map } from "maplibre-gl"; +import maplibregl from "maplibre-gl"; + +interface MapProps extends React.InputHTMLAttributes { + latitude?: number; + longitude?: number; +} + +export type MapContextValue = { + map: Map | null; +}; + +export const MapContext = React.createContext({ + map: null, +}); + +export default function MyMap({ latitude = 7, longitude = 52 }: MapProps) { + const mapContainer = useRef(null); + const [mapInstance, setMapInstance] = useState(null); + + const { current: contextValue } = useRef({ map: null }); + + useEffect(() => { + if (contextValue.map) return; //stops map from intializing more than once + + let map: Map; + + const initialState = { + lng: longitude, + lat: latitude, + zoom: 2, + }; + + if (mapContainer.current) { + map = new maplibregl.Map({ + container: mapContainer.current, + style: `https://api.maptiler.com/maps/streets/style.json?key=${ENV.MAPTILER_KEY}`, + center: [initialState.lng, initialState.lat], + zoom: initialState.zoom, + }); + + contextValue.map = map; + + setMapInstance(map); + } + + return () => { + if (mapInstance) { + map.remove(); + } + }; + }, [contextValue, longitude, latitude]); + + return ( +
+
+ +
+
+ ); +} diff --git a/app/components/bottomBar/BottomBar.tsx b/app/components/bottomBar/BottomBar.tsx new file mode 100644 index 000000000..617ce86fa --- /dev/null +++ b/app/components/bottomBar/BottomBar.tsx @@ -0,0 +1,88 @@ +import { useState } from "react"; +import SingleValue from "./SingleValue"; +import { XCircleIcon, MinusCircleIcon, ChevronDoubleUpIcon } from "@heroicons/react/24/solid"; +import { Link } from "@remix-run/react"; + +interface BottomBarProps { + id: string; + name: string; + sensors: Array; + lastUpdate: string; +} + +interface SensorProps { + _id: string; + icon: string; + lastMeasurement: LastMeasurementProps; + sensorType: string; + title: string; + unit: string; +} + +interface LastMeasurementProps { + createdAt: string; + value: string; +} + +export default function BottomBar(device: BottomBarProps) { + const [isOpen, setIsOpen] = useState(true); + return ( +
+
+
+
+

{device.name}

+
+
+
+

Letzte Messung:

+

+ {" "} + {device.lastUpdate} +

+
+
+
+ { + setIsOpen(!isOpen); + }} + className="h-6 w-6 cursor-pointer text-white lg:h-8 lg:w-8" + /> +
+
+ + + +
+
+
+ {device.sensors.map((sensor: SensorProps) => { + return ( + + ); + })} +
+
+
{ + setIsOpen(!isOpen); + }} + className={ + "absolute bottom-5 left-1/2 cursor-pointer rounded-full bg-white p-2 hover:animate-bounce " + + (!isOpen ? "visible" : "hidden") + } + > + +
+
+ ); +} diff --git a/app/components/bottomBar/SingleValue.tsx b/app/components/bottomBar/SingleValue.tsx new file mode 100644 index 000000000..aabe695a9 --- /dev/null +++ b/app/components/bottomBar/SingleValue.tsx @@ -0,0 +1,25 @@ +interface SingleValueProps { + _id: string; + icon: string; + lastMeasurement: LastMeasurementProps; + sensorType: string; + title: string; + unit: string; +} + +interface LastMeasurementProps { + createdAt: string; + value: string; +} + +export default function SingleValue(sensor: SingleValueProps) { + return ( +
+
+ {sensor.lastMeasurement ? ({sensor.lastMeasurement.value}) : (xx)} +

{sensor.unit}

+
+

{sensor.title}

+
+ ); +} diff --git a/app/components/header/Header.tsx b/app/components/header/Header.tsx new file mode 100644 index 000000000..1f28d36d5 --- /dev/null +++ b/app/components/header/Header.tsx @@ -0,0 +1,13 @@ +import Home from "./home/Home"; +import NavBar from "./navBar/NavBar"; +import Menu from "./menu/Menu"; + +export default function Header() { + return ( +
+ + + +
+ ); +} \ No newline at end of file diff --git a/app/components/header/home/Home.tsx b/app/components/header/home/Home.tsx new file mode 100644 index 000000000..2340d6857 --- /dev/null +++ b/app/components/header/home/Home.tsx @@ -0,0 +1,13 @@ +import { Link } from "@remix-run/react"; + +export default function Home() { + return ( +
+ + + +
+ ); +} \ No newline at end of file diff --git a/app/components/header/menu/Menu.tsx b/app/components/header/menu/Menu.tsx new file mode 100644 index 000000000..056b0d74c --- /dev/null +++ b/app/components/header/menu/Menu.tsx @@ -0,0 +1,38 @@ +import * as Dialog from '@radix-ui/react-dialog'; +import { Link } from "@remix-run/react"; + + +export default function Menu() { + return ( +
+ + + + + + + + {/* Fill me with customized content */} + + Impressum + + + + + + + +
+ ); +} diff --git a/app/components/header/navBar/NavBar.tsx b/app/components/header/navBar/NavBar.tsx new file mode 100644 index 000000000..9055a4583 --- /dev/null +++ b/app/components/header/navBar/NavBar.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import * as NavigationMenu from '@radix-ui/react-navigation-menu'; + + +export default function NavBar() { + const [isHovered, setIsHovered] = React.useState(false); + + return ( +
setIsHovered(!isHovered)} onMouseLeave={() => setIsHovered(!isHovered)}> + + + + + {!isHovered ? +
+
+ + + +
+ Temperatur +
+
+
+ + + +
+ 01.01.2022 - 05.01-2022 +
+
+
+ : +
+ +
+ } +
+ +
+

Phänomene

+

10.10.2022

+

Karteneinstellungen

+
+
+ +
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 7b9fd9133..57166e454 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -4,9 +4,12 @@ import { Response } from "@remix-run/node"; import { RemixServer } from "@remix-run/react"; import isbot from "isbot"; import { renderToPipeableStream } from "react-dom/server"; +import { getEnv } from "./env.server"; const ABORT_DELAY = 5000; +global.ENV = getEnv(); + export default function handleRequest( request: Request, responseStatusCode: number, diff --git a/app/env.server.ts b/app/env.server.ts new file mode 100644 index 000000000..c8bbb83a3 --- /dev/null +++ b/app/env.server.ts @@ -0,0 +1,14 @@ +export function getEnv() { + return { + MAPTILER_KEY: process.env.MAPTILER_KEY, + }; +} + +type ENV = ReturnType; + +declare global { + var ENV: ENV; + interface Window { + ENV: ENV; + } +} diff --git a/app/root.tsx b/app/root.tsx index 1e33c68ec..655780d9f 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -7,7 +7,9 @@ import { Outlet, Scripts, ScrollRestoration, + useLoaderData, } from "@remix-run/react"; +import { getEnv } from "./env.server"; import { getUser } from "./session.server"; import tailwindStylesheetUrl from "./styles/tailwind.css"; @@ -58,10 +60,12 @@ export const meta: MetaFunction = () => ({ export async function loader({ request }: LoaderArgs) { return json({ user: await getUser(request), + ENV: getEnv(), }); } export default function App() { + const data = useLoaderData(); return ( @@ -72,6 +76,11 @@ export default function App() { +