Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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/"
62 changes: 62 additions & 0 deletions app/components/Map.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement> {
latitude?: number;
longitude?: number;
}

export type MapContextValue = {
map: Map | null;
};

export const MapContext = React.createContext<MapContextValue>({
map: null,
});

export default function MyMap({ latitude = 7, longitude = 52 }: MapProps) {
const mapContainer = useRef<HTMLDivElement>(null);
const [mapInstance, setMapInstance] = useState<Map | null>(null);

const { current: contextValue } = useRef<MapContextValue>({ 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 (
<div className="h-full min-h-full w-full">
<div ref={mapContainer} className="h-full w-full">
<MapContext.Provider value={contextValue}></MapContext.Provider>
</div>
</div>
);
}
88 changes: 88 additions & 0 deletions app/components/bottomBar/BottomBar.tsx
Original file line number Diff line number Diff line change
@@ -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<SensorProps>;
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<Boolean>(true);
return (
<div>
<div className={"bg-white " + (isOpen ? "animate-fade-in-up" : "hidden")}>
<div className="flex">
<div className="text-l basis-1/4 bg-green-300 pt-6 pb-6 text-center font-bold text-green-900 lg:text-3xl">
<p>{device.name}</p>
</div>
<div className="grid basis-3/4 content-center bg-green-900 pr-2 text-right text-sm text-white">
<div>
<p className="text-xs lg:inline lg:text-sm">Letzte Messung:</p>
<p className="text-xs lg:inline lg:text-sm">
{" "}
{device.lastUpdate}
</p>
</div>
</div>
<div className="flex items-center bg-green-900 pr-2">
<MinusCircleIcon
onClick={() => {
setIsOpen(!isOpen);
}}
className="h-6 w-6 cursor-pointer text-white lg:h-8 lg:w-8"
/>
</div>
<div className="flex items-center bg-green-900 pr-2">
<Link prefetch="intent" to="/explore">
<XCircleIcon className="h-6 w-6 cursor-pointer text-white lg:h-8 lg:w-8" />
</Link>
</div>
</div>
<div className="flex justify-center overflow-auto">
{device.sensors.map((sensor: SensorProps) => {
return (
<SingleValue
key={sensor._id}
_id={sensor._id}
icon={sensor.icon}
sensorType={sensor.sensorType}
title={sensor.title}
unit={sensor.unit}
lastMeasurement={sensor.lastMeasurement}
/>
);
})}
</div>
</div>
<div
onClick={() => {
setIsOpen(!isOpen);
}}
className={
"absolute bottom-5 left-1/2 cursor-pointer rounded-full bg-white p-2 hover:animate-bounce " +
(!isOpen ? "visible" : "hidden")
}
>
<ChevronDoubleUpIcon className="h-6 w-6 text-green-900" />
</div>
</div>
);
}
25 changes: 25 additions & 0 deletions app/components/bottomBar/SingleValue.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="border-grey-300 lg:mb-3 mt-3 flex-1 border-r border-l border-solid pl-3 pr-3 text-center text-l lg:text-2xl">
<div className="flex justify-center">
{sensor.lastMeasurement ? (<b>{sensor.lastMeasurement.value}</b>) : (<b>xx</b>)}
<p>{sensor.unit}</p>
</div>
<p className="text-sm lg:text-xl">{sensor.title}</p>
</div>
);
}
13 changes: 13 additions & 0 deletions app/components/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Home from "./home/Home";
import NavBar from "./navBar/NavBar";
import Menu from "./menu/Menu";

export default function Header() {
return (
<div className="flex items-center p-2 w-full h-14 fixed z-10 pointer-events-none">
<Home />
<NavBar />
<Menu />
</div>
);
}
13 changes: 13 additions & 0 deletions app/components/header/home/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Link } from "@remix-run/react";

export default function Home() {
return (
<div className="w-10 h-10 pointer-events-auto">
<Link to="/">
<button type="button" className="w-10 h-10 rounded-full text-black hover:bg-gray-200 bg-white">
<img src="/logo.png" alt="openSenseMapLogo" className="w-7 h-7 mx-auto" />
</button>
</Link>
</div>
);
}
38 changes: 38 additions & 0 deletions app/components/header/menu/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as Dialog from '@radix-ui/react-dialog';
import { Link } from "@remix-run/react";


export default function Menu() {
return (
<div className="box-border w-10 h-10 pointer-events-auto">
<Dialog.Root>
<Dialog.Trigger asChild>
<button type="button" className="w-10 h-10 rounded-full text-black text-center hover:bg-gray-200 bg-white">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6 mx-auto">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="bg-black opacity-25 fixed inset-0 z-50" />
<Dialog.Content className="data-[state=open]:animate-sidebarOpen data-[state=closed]:animate-sidebarClose fixed inset-y-0 right-0 w-1/2 h-[100%] rounded-[6px] bg-white p-[25px] shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] focus:outline-none z-50">
{/* Fill me with customized content */}
<Link to="/impressum">
Impressum
</Link>
<Dialog.Close asChild>
<button
className="absolute top-[10px] right-[10px] inline-flex h-[25px] w-[25px] items-center justify-center"
aria-label="Close"
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6 mx-auto">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
</div>
);
}
54 changes: 54 additions & 0 deletions app/components/header/navBar/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-1/2 h-10 mx-auto pointer-events-auto" onMouseEnter={() => setIsHovered(!isHovered)} onMouseLeave={() => setIsHovered(!isHovered)}>
<NavigationMenu.Root className="relative">
<NavigationMenu.List className="w-full h-10">
<NavigationMenu.Item className="w-full h-10">
<NavigationMenu.Trigger className="w-full h-10 bg-white data-[state=open]:rounded-t-[1.25rem] data-[state=closed]:rounded-[1.25rem]">
{!isHovered ?
<div className="flex items-center justify-around w-full h-10">
<div className="flex items-center justify-center rounded-full bg-orange-500 w-3/12 h-6">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-4 h-4 text-white">
<path strokeLinecap="round" strokeLinejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
</svg>
<div className="text-center text-white">
Temperatur
</div>
</div>
<div className="flex items-center justify-center rounded-full bg-blue-700 w-4/12 h-6">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-4 h-4 text-white">
<path strokeLinecap="round" strokeLinejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5m-9-6h.008v.008H12v-.008zM12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zM9.75 15h.008v.008H9.75V15zm0 2.25h.008v.008H9.75v-.008zM7.5 15h.008v.008H7.5V15zm0 2.25h.008v.008H7.5v-.008zm6.75-4.5h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V15zm0 2.25h.008v.008h-.008v-.008zm2.25-4.5h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5V15z" />
</svg>
<div className="text-center text-white">
01.01.2022 - 05.01-2022
</div>
</div>
</div>
:
<div className="flex items-center justify-around w-full h-10 p-2">
<input type="text" placeholder="Suche, Filter & Einstellungen" className="w-full h-6 rounded-full" />
</div>
}
</NavigationMenu.Trigger>
<NavigationMenu.Content className="flex items-center justify-around container mx-auto bg-white rounded-b-[1.25rem]">
<div>
<p>Phänomene</p>
<p>10.10.2022</p>
<p>Karteneinstellungen</p>
</div>
<div>
<input type="text" placeholder="Phänomene suchen" className="rounded-full" />
</div>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
</div>
);
}
3 changes: 3 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions app/env.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function getEnv() {
return {
MAPTILER_KEY: process.env.MAPTILER_KEY,
};
}

type ENV = ReturnType<typeof getEnv>;

declare global {
var ENV: ENV;
interface Window {
ENV: ENV;
}
}
9 changes: 9 additions & 0 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<typeof loader>();
return (
<html lang="en" className="h-full">
<head>
Expand All @@ -72,6 +76,11 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<script
dangerouslySetInnerHTML={{
__html: `window.ENV = ${JSON.stringify(data.ENV)}`,
}}
/>
<LiveReload />
</body>
</html>
Expand Down
26 changes: 26 additions & 0 deletions app/routes/explore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { LinksFunction } from "@remix-run/node";
import { Outlet } from "@remix-run/react";
import Map from "~/components/Map";
import maplibregl from "maplibre-gl/dist/maplibre-gl.css";
import Header from "~/components/header/Header";

export const links: LinksFunction = () => {
return [
{
rel: "stylesheet",
href: maplibregl,
},
];
};

export default function Explore() {
return (
<div className="h-full w-full">
<Header />
<Map />
<main className="absolute bottom-0 z-10 w-full">
<Outlet />
</main>
</div>
);
}
Loading