move types to shared, add first stuff

This commit is contained in:
2023-05-04 20:40:10 +02:00
parent 1ed359f602
commit df097ab4f6
23 changed files with 1118 additions and 214 deletions

View File

@@ -28,5 +28,8 @@
"error",
"always"
]
}
},
"ignorePatterns": [
"**/vite.config.ts"
]
}

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>IASsure-WX</title>
</head>
<body>
<div id="root"></div>

File diff suppressed because it is too large Load Diff

View File

@@ -9,10 +9,18 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.4.0",
"bootstrap": "^5.2.3",
"leaflet": "^1.9.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-bootstrap": "^2.7.4",
"react-dom": "^18.2.0",
"react-leaflet": "^4.2.1",
"react-router": "^6.11.1"
},
"devDependencies": {
"@types/leaflet": "^1.9.3",
"@types/node": "^18.16.3",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@typescript-eslint/eslint-plugin": "^5.38.1",
@@ -27,4 +35,4 @@
"typescript": "^4.9.4",
"vite": "^4.0.0"
}
}
}

View File

@@ -1,41 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,33 +1,203 @@
import { useState } from 'react';
import reactLogo from './assets/react.svg';
import './App.css';
import 'leaflet/dist/leaflet.css';
import 'bootstrap/dist/css/bootstrap.css';
import { MapContainer, TileLayer, LayersControl, Circle, LayerGroup, Popup, Marker } from 'react-leaflet';
import { Button, Modal, Table } from 'react-bootstrap';
import { useEffect, useState } from 'react';
import wxService from './services/wx.service';
import { WxRegion } from '@shared/types/config.types';
import { WxData } from '../../shared/src/types/wx.types';
import { DivIcon } from 'leaflet';
function App() {
const [count, setCount] = useState(0);
const [showModal, setShowModal] = useState(false);
const [loading, setLoading] = useState(false);
const [regions, setRegions] = useState<WxRegion[]>([]);
const [selectedRegion, setSelectedRegion] = useState<string>('');
const [wxData, setWxData] = useState<WxData | null>(null);
function getSetShowModal(val: boolean) {
return () => setShowModal(val);
}
useEffect(() => {
if (selectedRegion) {
return;
}
setLoading(true);
wxService.getRegions()
.then(regionsResponse => {
setRegions(regionsResponse);
setShowModal(true);
setLoading(false);
});
}, []);
useEffect(() => {
setLoading(true);
wxService.getWxData(selectedRegion).then(wxResponse => {
setWxData(wxResponse);
setShowModal(false);
setLoading(false);
console.log(wxResponse);
});
}, [selectedRegion]);
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
<>
<Modal
show={showModal}
backdrop="static"
keyboard={false}
>
<Modal.Header closeButton>
<Modal.Title>Regions</Modal.Title>
</Modal.Header>
<Modal.Body>
<Table striped>
<thead>
<tr>
<th>#</th>
</tr>
</thead>
<tbody>
{regions.length ? regions.map((region, idx) => <tr key={idx}>
<td>{idx + 1}</td>
<td>{region.identifier}</td>
<td>{region.fixes.length} Fixes</td>
<td><Button variant='primary' type='button' onClick={() => setSelectedRegion(region.identifier)}>Select</Button></td>
</tr>) : <>
<tr><td className='p-2 text-center' colSpan={5}>- no regions defined -</td></tr>
</>}
</tbody>
</Table>
</Modal.Body>
</Modal>
<div className='position-absolute bottom-0 p-2' style={{ zIndex: 999 }}>
<Button variant='outline-secondary' type='button' onClick={getSetShowModal(true)}> {selectedRegion && `(Selected: ${selectedRegion})`}</Button>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount(count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</div>
<MapContainer style={{ height: '100vh', width: '100vw' }} center={[50.033306, 8.570456]} zoom={7} scrollWheelZoom={true}>
<TileLayer
url='https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png'
attribution={[
'<a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>',
'<a href="https://carto.com/attributions" target="_blank">CARTO</a>',
'Weather data by Open-Meteo.com (<a href="https://open-meteo.com" target="_blank">open-meteo.com</a>)',
'IASsure-WX: <a href="https://fsperath.de" target="_blank">Fionn Sperath</a> and <a href="https://github.com/dotFionn/iassure-wx/graphs/contributors" target="_blank">contributors</a>',
].map(str => `&copy; ${str}`).join(' | ')}
subdomains={'abc'}
maxZoom={20}
minZoom={0}
/>
<LayersControl position='topright'>
<LayersControl.Overlay checked name='Enable labels'>
<TileLayer
url='https://{s}.basemaps.cartocdn.com/dark_only_labels/{z}/{x}/{y}.png'
subdomains={'abc'}
maxZoom={20}
minZoom={0}
/>
</LayersControl.Overlay>
<LayersControl.Overlay checked name="Points">
<LayerGroup>
{/* {dots.map((dot, index) => <Circle key={index} center={dot.pos} radius={400} pathOptions={{ fillColor: dot.color, color: dot.color }}>
<Popup>{dot.label}</Popup>
</Circle>)} */}
{Object.entries(wxData?.data || {}).map(([fix, data], idx) => (
<>
{/* <Circle key={idx} center={[Number(data.coords.lat), Number(data.coords.long)]} radius={5000} pathOptions={{ fillColor: 'green', color: 'green', fillOpacity: 1 }}>
<Popup>
<Table>
<thead>
<tr><th colSpan={3} className='text-center'>{fix}</th></tr>
<tr><th>Lvl</th><th>Temp/K</th><th>Wind</th></tr>
</thead>
<tbody>
{Object.entries(data.levels).map(([lvl, lvlData], lvlIdx) => (
<tr key={lvlIdx}>
<td>{lvl}</td>
<td>{Math.round(Number(lvlData['T(K)']))}</td>
<td>{lvlData.windhdg}° / {lvlData.windspeed}kts</td>
</tr>
))}
</tbody>
</Table>
</Popup>
</Circle> */}
<Marker
position={[Number(data.coords.lat), Number(data.coords.long)]}
key={fix}
title={fix}
icon={new DivIcon({ html: `⨀&nbsp;<span class="fw-bold">${fix}</span>`, className: 'bg-none text-warning' })}
>
<Popup>
<Table>
<thead>
<tr><th colSpan={3} className='text-center'>{fix}</th></tr>
<tr><th>Lvl</th><th>Temp/K</th><th>Wind</th></tr>
</thead>
<tbody>
{Object.entries(data.levels).map(([lvl, lvlData], lvlIdx) => (
<tr key={lvlIdx}>
<td>{lvl}</td>
<td>{Math.round(Number(lvlData['T(K)']))}</td>
<td>{lvlData.windhdg}° / {lvlData.windspeed}kts</td>
</tr>
))}
</tbody>
</Table>
</Popup>
</Marker>
</>
))}
</LayerGroup>
</LayersControl.Overlay>
{/* {populatedRoutes.filter(r => !!r).map((populatedRoute, idx) => {
if (!populatedRoute) {
return null;
}
const line: [number, number][] = [];
const dots: { pos: [number, number], color: string, label: string }[] = [];
if (populatedRoute.adep) {
line.push([populatedRoute.adep.lat, populatedRoute.adep.lon]);
dots.push({ pos: [populatedRoute.adep.lat, populatedRoute.adep.lon], color: 'red', label: `${populatedRoute.adep.icao} / ${populatedRoute.adep.name}` });
}
for (const point of populatedRoute.points) {
line.push([point.lat, point.lon]);
dots.push({ pos: [point.lat, point.lon], color: 'blue', label: point.identifier });
}
if (populatedRoute.ades) {
line.push([populatedRoute.ades.lat, populatedRoute.ades.lon]);
dots.push({ pos: [populatedRoute.ades.lat, populatedRoute.ades.lon], color: 'green', label: [populatedRoute.ades.icao, populatedRoute.ades.name].join(' ') });
}
return <LayersControl.Overlay checked name={`${populatedRoute.adep?.icao}${populatedRoute.ades?.icao}`} key={idx}>
<LayerGroup>
<Polyline pathOptions={{ color: 'yellow' }} positions={line} />
{dots.map((dot, index) => <Circle key={index} center={dot.pos} radius={400} pathOptions={{ fillColor: dot.color, color: dot.color }}>
<Popup>{dot.label}</Popup>
</Circle>)}
</LayerGroup>
</LayersControl.Overlay>;
})} */}
</LayersControl>
</MapContainer>
</>
);
}

View File

@@ -1,70 +1,6 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
height: 100vh;
width: 100vw;
overflow: hidden;
}

View File

@@ -0,0 +1,18 @@
import { WxRegion } from '@shared/types/config.types';
import { WxData } from '@shared/types/wx.types';
import axios from 'axios';
async function getRegions(): Promise<WxRegion[]> {
const response = await axios.get<WxRegion[]>('/api/regions');
return response.data;
}
async function getWxData(region: string): Promise<WxData> {
const response = await axios.get<WxData>(`/api/regions/${region}/wx`);
return response.data;
}
export default {
getRegions,
getWxData,
};

View File

@@ -14,7 +14,10 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"paths": {
"@shared/*": ["../shared/src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]

View File

@@ -1,7 +1,21 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import * as path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
resolve: {
alias: [{ find: '@shared', replacement: path.resolve(__dirname, '..', 'shared', 'src') }],
},
server: {
proxy: {
'/api': {
target: 'https://wx-dev.vateud.de',
changeOrigin: true,
// secure: false,
// ws: true,
},
},
},
});