chore: initial commit

This commit is contained in:
dancingCycle 2023-02-08 14:31:03 +01:00
parent 9362d44eb9
commit 7e63b3462d
22 changed files with 12224 additions and 0 deletions

6
.babelrc Normal file
View File

@ -0,0 +1,6 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}

27
app/app.jsx Normal file
View File

@ -0,0 +1,27 @@
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
/*some stylesheet is required to use react-bootstrip components*/
import 'bootstrap/dist/css/bootstrap.min.css';
import Contact from './pages/contact';
import Home from './pages/home';
import Map from './pages/map-page';
import NavBar from './components/nav-bar';
export default function App() {
return (
//BrowserRouter is the router implementation for HTML5 browsers
//Link enables Routes on an anchor tag
//Switch returns only the first matching route rather than all
//Route is the conditionally shown component //based on matching a path to a URL
<BrowserRouter>
<NavBar />
<Routes>
<Route path="/" element={<Map />} />
<Route path="/map" element={<Map />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 172.83 143.31" style="enable-background:new 0 0 172.83 143.31;" xml:space="preserve">
<style type="text/css">
.st0{fill:#4E4697;}
</style>
<path class="st0" d="M159.98,95.78c-1.95-4.54-5.55-8.69-10.82-12.47c-1.88-1.34-4.64-2.92-8.28-4.8c4.58-2.89,7.92-5.35,9.99-7.37
c3.94-3.77,6.51-7.74,7.72-11.91c1.23-4.21,1.18-8.44-0.14-12.8c-0.73-2.39-1.8-4.62-3.2-6.65l-1.6-2.07
c-1.01-1.21-2.16-2.31-3.45-3.37c-4.04-3.29-9.07-5.2-15.09-5.73c-5.9-0.51-12.07,0.17-18.51,2.07c-0.05,0-0.06,0.02-0.11,0.02
c-0.06,0.03-0.14,0.05-0.2,0.07c-3.81,1.17-7.61,2.64-11.07,4.39c-3.05,1.55-5.86,3.26-8.13,5.11l0.02,0.07h-0.02v82.31
c5.55,3.8,12.43,7.3,21.44,9.12c7.12,1.45,13.78,1.5,19.98,0.13c5.33-1.17,9.82-3.27,13.48-6.35c0.62-0.48,1.22-1.03,1.79-1.58
c3.28-3.24,5.55-6.89,6.83-10.96c0.29-0.86,0.52-1.73,0.71-2.64C162.38,105.2,161.92,100.33,159.98,95.78z M109.63,45.63
c1.82-0.9,3.78-1.7,5.75-2.42c0.04-0.02,0.08-0.04,0.12-0.04l3.6-1.2c0.07-0.03,0.13-0.05,0.19-0.05
c7.71-2.32,13.73-2.64,18.11-0.97c2.76,1.06,4.87,2.56,6.37,4.48c0.9,1.15,1.56,2.46,2,3.93c1.05,3.39,0.61,6.56-1.23,9.51
c-0.5,0.81-1.4,2.15-2.13,2.93c-1.7,2.07-5.03,4.64-8.28,6.65c-2.34,1.45-4.91,2.64-7.57,3.36c-0.01,0.01-1.41,0.34-1.41,0.34
c-2.18,0.63-8.53,0.69-15.52,0.66V45.63z M148.38,108.82c-0.55,2.75-1.87,5.12-3.93,7.13c-0.19,0.2-0.39,0.39-0.59,0.57
c-2.38,2.15-5.67,3.56-9.81,4.23c-4.15,0.66-8.75,0.48-13.78-0.55c-2.7-0.55-6.72-2.19-10.64-4.08V83.79
c3.96-0.09,7.5-0.15,10.71,0.1h0.07c0.39,0,0.77,0.01,1.16,0.04c2.67,0.17,5.38,0.86,7.98,1.89c0.01-0.01,0.02,0,0.04,0.01
c4.37,1.72,8.56,4.3,11.54,7c2.27,1.97,4.48,4.53,5.78,6.81c0.15,0.23,0.28,0.46,0.4,0.7C148.65,102.96,149.01,105.81,148.38,108.82
z"/>
<ellipse transform="matrix(0.9347 -0.3553 0.3553 0.9347 0.6343 30.8075)" class="st0" cx="84.18" cy="13.68" rx="8.33" ry="8.33"/>
<path class="st0" d="M91.15,38.16v84.61c0,5.54-3.12,10.04-6.97,10.04s-6.97-4.5-6.97-10.04V38.16c0-5.54,3.12-10.04,6.97-10.04
S91.15,32.62,91.15,38.16z"/>
<path class="st0" d="M23.43,49.65c-1.03,3.43-0.62,6.63,1.24,9.61c1.82,2.96,7.61,7.21,17.38,12.72
c9.78,5.51,16.35,9.73,19.72,12.66c4.9,4.28,8.11,8.8,9.63,13.55c1.52,4.75,1.52,9.68-0.02,14.8c-1.52,5.08-4.44,9.41-8.75,13
c-4.32,3.59-9.63,5.77-15.93,6.58c-6.3,0.81-12.93,0.14-19.88-1.98c-8.31-2.53-14.39-6.55-19.42-10.64
c-0.05-0.04-0.1-0.08-0.15-0.12c-0.25-0.2-0.5-0.4-0.74-0.6c-3.26-2.86-4.49-6.86-2.71-9.32c1.93-2.67,6.77-2.28,10.6,0.38
c0.38,0.3,1.87,1.47,2.52,1.94c3.71,2.71,8.45,5.56,12.7,6.85c4.91,1.5,9.47,2.11,13.66,1.82c4.19-0.29,7.58-1.4,10.17-3.34
c2.58-1.93,4.32-4.37,5.2-7.33c0.9-2.99,0.8-5.87-0.3-8.66c-1.1-2.77-3.46-5.54-7.07-8.3c-2.32-1.79-7.63-5.1-15.94-9.93
c-8.31-4.84-13.98-8.73-17-11.69c-3.93-3.8-6.51-7.82-7.73-12.05c-1.22-4.23-1.17-8.54,0.15-12.92c1.44-4.82,4.19-8.89,8.23-12.23
c4.05-3.34,9.07-5.27,15.08-5.8c6.01-0.54,12.4-0.16,18.84,2.19c4.28,1.56,6.41,2.42,8.33,3.25c1.12,0.48,3.12,1.27,4.82,1.98
c0.22,0.09,0.98,0.41,1.06,0.45c4.23,1.95,6.71,5.58,5.59,8.25c-1.09,2.57-5.18,3.34-9.37,1.83c-0.05-0.02-0.11-0.04-0.17-0.06
c-3.65-1.15-5.71-2.17-13.07-4.42c-7.79-2.37-13.89-2.7-18.29-0.99C27.41,42.87,24.61,45.7,23.43,49.65z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,35 @@
import getIconUrl from './icon-url';
/*return icon object*/
export default function getIcon(ptByIfleet){
if(ptByIfleet===undefined || ptByIfleet===null){
//console.log('getIcon(): ptByIfleet NOT available')
return null;
}
else{
//console.log('getIcon(): ptByIfleet available')
const icon = new L.Icon({
/*path to icon graphic*/
iconUrl: getIconUrl(ptByIfleet.route_type),
/*path to graphic used for high resolution monitors*/
iconRetinaUrl: getIconUrl(ptByIfleet.route_type),
popupAnchor: [-0, -0],
/*size of the icon in width and hight*/
iconSize: [32,32],
/*determine how the popup is positions relative to the actual point on the map*/
popupAnchor:[0,-10],
/*determine how the image is positions relative to the actual point on the map*/
iconAnchor: null,
/*path to shadow graphic*/
shadowUrl: null,
/*size of the shadow in width and hight*/
shadowSize: null,
/*determine how the mage is positions relative to the actual point on the map*/
shadowAnchor: null,
className: 'marker-msg'
});
//console.log('getIcon(): icon available')
return icon;
}
}

View File

@ -0,0 +1,14 @@
import bfly from '../../assets/Logo_SIB_electricindigo.svg';
import bus from '../../assets/bus.svg';
import train from '../../assets/train.svg';
import tram from '../../assets/tram.svg';
/* return URL that fits GTFS route_type*/
export default function getIconUrl(routeTypeEnum){
//console.log('getIconUrl() routeTypeEnum: '+routeTypeEnum);
return routeTypeEnum===0?tram:
routeTypeEnum===1?bfly:
routeTypeEnum===2?train:
routeTypeEnum===3?bus:
bfly;
}

View File

@ -0,0 +1,28 @@
import bfly from '../../assets/Logo_SIB_electricindigo.svg';
/*return icon object*/
export default function getIcon(){
//console.log('getIcon(): ptByIfleet available')
const icon = new L.Icon({
/*path to icon graphic*/
iconUrl: bfly,
/*path to graphic used for high resolution monitors*/
iconRetinaUrl: bfly,
popupAnchor: [-0, -0],
/*size of the icon in width and hight*/
iconSize: [32,32],
/*determine how the popup is positions relative to the actual point on the map*/
popupAnchor:[0,-10],
/*determine how the image is positions relative to the actual point on the map*/
iconAnchor: null,
/*path to shadow graphic*/
shadowUrl: null,
/*size of the shadow in width and hight*/
shadowSize: null,
/*determine how the mage is positions relative to the actual point on the map*/
shadowAnchor: null,
className: 'marker-msg'
});
//console.log('getIcon(): icon available')
return icon;
}

View File

@ -0,0 +1,4 @@
/*set up the height of*/
.leaflet-container {
height: 85vh;
}

View File

@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import {MapContainer,TileLayer} from 'react-leaflet';
/*JS module import (vs cdn or style link)*/
import 'leaflet/dist/leaflet.css'
import './map.css';
import MarkerElem from './marker-elem';
export default function Map({elements}) {
/*lat and lon of Braunschweig,DE*/
const position = [52.26594, 10.52673]
//TODO make this switch available via configuration!
const hasGtfs = false;
return (
<>
<MapContainer
center={position}
zoom={10}
minZoom={2}
scrollWheelZoom={true}
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{
elements.map(function(value,key) {
//console.log(`key: ${key}, tripId: ${value.tripId}`);
return <MarkerElem key={value.id} index={value.id} element={value}/>;
})
}
</MapContainer>
</>
);
}
Map.propTypes = {
elements: PropTypes.array
};

View File

@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Marker} from 'react-leaflet';
import PopupElem from './popup-elem';
import getIcon from './icon';
export default function MarkerElemPlus({element}){
if(element===undefined || element===null){
console.error('element undefined or null');
return null;
}else{
const markerIcon=getIcon();
if(markerIcon===null){
//TODO Handle issue!
console.error('ERROR: icon null');
return null;
}else if(element.lat===undefined||element.lat===null){
//TODO Handle issue!
console.error('ERROR: element lat undefined or null');
return null;
}else if(element.lon===undefined||element.lon===null){
//TODO Handle issue!
console.error('ERROR: element lon undefined or null');
return null;
}else{
return(
<>
<Marker
position={[element.lat,element.lon]}
icon={markerIcon}
>
<PopupElem element={element} />
</Marker>
</>
);
}
}
};
MarkerElemPlus.propTypes = {
element: PropTypes.object
};

View File

@ -0,0 +1,22 @@
import React, {useEffect,useState} from 'react';
import PropTypes from 'prop-types';
import MarkerElemPlus from './marker-elem-plus';
export default function MarkerElem({ element }){
if(element===undefined || element===null){
console.error('element undefined or null');
return null;
}else{
return(
<>
<MarkerElemPlus
element={element}
/>
</>
);
}
};
MarkerElem.propTypes = {
element: PropTypes.object
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Popup} from 'react-leaflet';
export default function PopupElem({element}){
return (
<>
<Popup>
id: {element.id} <br/>
name: {element.name} <br/>
lat: {element.lat} <br/>
lon: {element.lon}
</Popup>
</>
);
};
PopupElem.propTypes = {
element: PropTypes.object
};

27
app/components/nav-bar.js Normal file
View File

@ -0,0 +1,27 @@
import React from 'react';
import { Navbar, Nav } from 'react-bootstrap';
import { LinkContainer } from 'react-router-bootstrap';
function NavigationBar () {
return (
<Navbar collapseOnSelect fixed="top" bg="dark" expand="xxl" variant="dark">
//TODO make brand available through configuration
<Navbar.Brand href="/">GOMPASS</Navbar.Brand>
<Navbar.Toggle aria-controls="basic-navbar-nav" />
<Navbar.Collapse id="basic-navbar-nav">
<Nav className="mr-auto">
<LinkContainer to="/map">
<Nav.Link>Second Hand</Nav.Link>
</LinkContainer>
</Nav>
<Nav className="mr-auto">
<LinkContainer to="/contact">
<Nav.Link>Contact</Nav.Link>
</LinkContainer>
</Nav>
</Navbar.Collapse>
</Navbar>
);
}
export default NavigationBar;

14
app/index.jsx Normal file
View File

@ -0,0 +1,14 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './app';
//TODO remove debugging
if (process.env.NODE_ENV !== 'production') {
console.log('development mode');
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<App tab="home" />
</React.StrictMode>
);

34
app/pages/contact.js Normal file
View File

@ -0,0 +1,34 @@
import React from 'react';
import packageInfo from '../../package.json'
const VERSION = packageInfo.version;
const Contact = () => {
return (
<>
<h2>About this website</h2>
<p>
For questions about this website please do not hesitate to reach out to dialog (at) swingbe (dot) de.
</p>
<h2>Imprint</h2>
<address>
<strong>
Stefan Begerad, Software Ingenieur Begerad
</strong>
<br />
c/o WTF Kooperative eG, Hinterhaus, 3. OG
<br />
Forsmannstr. 14b
<br />
22303 Hamburg
<br />
Deutschland
<br />
</address>
<h2>Other</h2>
<p>
Version: {VERSION}
</p>
</>
);
};
export default Contact;

8
app/pages/home.jsx Normal file
View File

@ -0,0 +1,8 @@
import React from 'react';
export default function Home() {
return (
<>
<h1>Home</h1>
</>
);
}

96
app/pages/map-page.jsx Normal file
View File

@ -0,0 +1,96 @@
import React, {useEffect,useState} from 'react';
import axios from 'axios';
import qs from 'qs';
import Map from '../components/map/map';
const data = qs.stringify(
{
'data': '[bbox:51.990800124058914,10.045623779296875,52.552976197007524,11.01104736328125][out:json];(node["shop"="second_hand"];way["shop"="second_hand"];relation["shop"="second_hand"];);out center;'
}
);
//TODO Make fields available via configuration!
const interval=3600000;
const opInstance='https://overpass-api.de/api/interpreter';
const opInstanceZ='https://z.overpass-api.de/api/interpreter';
const config = {
method: 'post',
maxBodyLength: Infinity,
url: opInstanceZ,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data : data
};
function parseOsmElem(response){
let elemAry=[];
if(response.data){
let elements=response.data.elements;
let no=elements.length;
//console.log('no: '+no);
for(let i = 0; i < no; i++) {
let elemObj={};
elemObj.id=elements[i].id;
elemObj.name=elements[i].tags.name;
if(elements[i].tags.name===undefined){
//TODO Handle issue!
console.error('ERROR: OSM element does NOT include name tag');
}else if(elements[i].type==='node'){
elemObj.lat=elements[i].lat;
elemObj.lon=elements[i].lon;
}else if(elements[i].type==='way'){
elemObj.lat=elements[i].center.lat;
elemObj.lon=elements[i].center.lon;
}else{
console.error('ERROR: OSM element NOT supported');
}
//console.log('elem name: '+elemObj.name);
//console.log('elem lat: '+elemObj.lat);
//console.log('elem lon: '+elemObj.lon);
elemAry.push(elemObj);
//console.error('elemAry no: '+elemAry.length);
}
}else{
console.error('ERROR: response NOT available');
}
return elemAry;
};
export default function MapPage() {
/*storage*/
const [ary, setAry] = useState([]);
const getData= async ()=>{
//console.log('getData() start...');
try {
/*TODO handle errors: https://www.valentinog.com/blog/await-react/*/
const res = await axios(config);
let elemAry=parseOsmElem(res);
setAry((ary) => elemAry);
} catch (err) {
console.error('ERROR: err.message: ' + err.message);
}
//console.log('getData() done.');
};
useEffect(()=>{
/*do not wait the interval when component loads the first time*/
getData();
/*refresh data periodically*/
const intervalCall=setInterval(()=>{
getData();
}, interval);
/*TODO adjust interval, make it available via config file*/
return ()=>{
/*clean up*/
clearInterval(intervalCall);
};
},[]);
return (
<>
<Map elements={ary}/>
</>
);
}

53
config/webpack.common.js Normal file
View File

@ -0,0 +1,53 @@
//generate a HTML5 file including all webpack bundles in the body using script tags
const HtmlWebpackPlugin = require('html-webpack-plugin');
//path is used to resolve properly across the OS
const path = require('path');
module.exports = {
//bundle *.js from this entry point
entry: path.resolve(__dirname, '../app/index.jsx'),
//create output file to be linked to index.html
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../dist'),
clean: true,
},
module: {
rules: [
{
//test all *.js using babel-loader
//test all *.jsx (e.g. React.js) using babel-loader
test: /\.(js|jsx)$/,
exclude: /node_modules/,
include: path.resolve(__dirname, '../app'),
use: ['babel-loader'],
},
{
//test all *.css using style-loader and css-loader
test: /\.css$/i,
use: ["style-loader", "css-loader"],
},
{
//test all *.svg using svg-loader
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10000,
},
},
],
},
]
},
resolve: {
extensions: ['*', '.js', '.jsx'],
},
plugins: [
// create a plugin instance so that you can use it several times anywhere
new HtmlWebpackPlugin({
title: 'Production',
template: path.resolve(__dirname, "../public/index.html")
}),
],
};

14
config/webpack.dev.js Normal file
View File

@ -0,0 +1,14 @@
//path is used to resolve properly across the OS
const path = require('path');
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
//merge() calls in the environment-specific configuration to include commons
module.exports = merge(common, {
//set development mode
mode: 'development',
//enable strong source mapping
devtool: 'inline-source-map',
devServer: {
static: path.resolve(__dirname, '../build'),
},
});

8
config/webpack.prod.js Normal file
View File

@ -0,0 +1,8 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
//source maps encouraged in production
//choose mapping with fairly quick build speed like source-map
devtool: 'source-map',
});

11631
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

46
package.json Normal file
View File

@ -0,0 +1,46 @@
{
"private": true,
"name": "gompass",
"description": "gompass",
"version": "0.1.0",
"main": "index.js",
"keywords": [
"public",
"compass",
"gompass"
],
"author": "Software Ingenieur Begerad <dialog@SwIngBe.de>",
"license": "GPL-3.0-or-later",
"engines": {
"node": ">=10"
},
"scripts": {
"start": "webpack serve --config config/webpack.dev.js",
"build": "webpack --config config/webpack.prod.js"
},
"devDependencies": {
"@babel/core": "7.19.1",
"@babel/preset-env": "7.19.1",
"@babel/preset-react": "7.18.6",
"babel-loader": "8.2.5",
"css-loader": "6.7.1",
"html-webpack-plugin": "5.5.0",
"style-loader": "3.3.1",
"svg-url-loader": "8.0.0",
"webpack": "5.74.0",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.11.0",
"webpack-merge": "5.8.0"
},
"dependencies": {
"axios": "0.27.2",
"bootstrap": "5.2.1",
"leaflet": "1.8.0",
"qs": "^6.11.0",
"react": "18.2.0",
"react-bootstrap": "2.5.0",
"react-dom": "18.2.0",
"react-leaflet": "4.0.2",
"react-router-bootstrap": "0.26.2"
}
}

22
public/index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- load resources for bootstrap -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
crossorigin="anonymous">
<!-- avoid content hiding behind react-bootstrap navbar -->
<style type="text/css">
body {
padding-top: 100px;
}
</style>
<title>Gompass</title>
</head>
<body>
<div id="root"></div>
</body>
</html>