This commit is contained in:
Paul Bienkowski 2021-11-27 22:44:03 +01:00
parent 8dec4c8262
commit 75323ebc79
15 changed files with 1138 additions and 79 deletions

View file

@ -5,6 +5,6 @@ ADD package.json package-lock.json /opt/obs/frontend/
RUN echo update-notifier=false >> ~/.npmrc RUN echo update-notifier=false >> ~/.npmrc
RUN npm ci RUN npm ci
ADD tsconfig.json index.html webpack.config.js /opt/obs/frontend/ ADD tsconfig.json webpack.config.js /opt/obs/frontend/
ADD public/ /opt/obs/frontend/public/ ADD public/ /opt/obs/frontend/public/
ADD src/ /opt/obs/frontend/src/ ADD src/ /opt/obs/frontend/src/

View file

@ -9,7 +9,7 @@ ADD package.json package-lock.json /opt/obs/frontend/
RUN echo update-notifier=false >> ~/.npmrc RUN echo update-notifier=false >> ~/.npmrc
RUN npm ci RUN npm ci
ADD tsconfig.json index.html webpack.config.js /opt/obs/frontend/ ADD tsconfig.json webpack.config.js /opt/obs/frontend/
ADD public/ /opt/obs/frontend/public/ ADD public/ /opt/obs/frontend/public/
ADD src/ /opt/obs/frontend/src/ ADD src/ /opt/obs/frontend/src/

View file

@ -3,6 +3,7 @@
"loginUrl": "http://localhost:3000/login", "loginUrl": "http://localhost:3000/login",
"imprintUrl": "https://example.com/imprint", "imprintUrl": "https://example.com/imprint",
"privacyPolicyUrl": "https://example.com/privacy", "privacyPolicyUrl": "https://example.com/privacy",
"basename": "/",
"mapTileset": { "mapTileset": {
"url": "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png", "url": "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png",
"minZoom": 0, "minZoom": 0,

View file

@ -3,6 +3,7 @@
"loginUrl": "https://portal.example.com/login", "loginUrl": "https://portal.example.com/login",
"imprintUrl": "https://example.com/imprint", "imprintUrl": "https://example.com/imprint",
"privacyPolicyUrl": "https://example.com/privacy", "privacyPolicyUrl": "https://example.com/privacy",
"basename": "/",
"mapTileset": { "mapTileset": {
"url": "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png", "url": "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png",
"minZoom": 0, "minZoom": 0,

View file

@ -1,22 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<base href="%PUBLIC_URL%">
<meta charset="utf-8" />
<link rel="icon" href="public/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Upload the tracks recorded with your OpenBikeSensor device to this portal and participate in the accumulation of Open Data."
/>
<link rel="apple-touch-icon" href="public/apple-touch-icon.png" />
<link rel="manifest" href="public/manifest.json" />
<title>OpenBikeSensor Portal</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="./src/index.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,13 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "webpack-dev-server", "start": "webpack-dev-server",
"build": "webpack" "build": "NODE_ENV=production webpack"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.16.3",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fomantic-ui-less": "^2.8.8",
"luxon": "^1.28.0", "luxon": "^1.28.0",
"maplibre-gl": "^1.15.2", "maplibre-gl": "^1.15.2",
"pkce": "^1.0.0-beta2", "pkce": "^1.0.0-beta2",
@ -25,7 +27,6 @@
"rxjs": "^6.6.7", "rxjs": "^6.6.7",
"rxjs-hooks": "^0.6.2", "rxjs-hooks": "^0.6.2",
"sass": "^1.43.5", "sass": "^1.43.5",
"fomantic-ui-less": "^2.8.8",
"semantic-ui-react": "^2.0.4", "semantic-ui-react": "^2.0.4",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typescript": "^4.5.2" "typescript": "^4.5.2"
@ -47,12 +48,13 @@
"last 1 safari version" "last 1 safari version"
] ]
}, },
"proxy": "http://api:3000",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.0", "@babel/core": "^7.16.0",
"@babel/plugin-transform-runtime": "^7.16.4",
"@babel/preset-env": "^7.16.4", "@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0", "@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0", "@babel/preset-typescript": "^7.16.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.2",
"@types/lodash": "^4.14.177", "@types/lodash": "^4.14.177",
"@types/node": "^14.17.34", "@types/node": "^14.17.34",
"@types/react": "^17.0.37", "@types/react": "^17.0.37",
@ -60,8 +62,10 @@
"@types/react-redux": "^7.1.20", "@types/react-redux": "^7.1.20",
"@types/react-router-dom": "^5.3.2", "@types/react-router-dom": "^5.3.2",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"css-loader": "^6.5.1", "css-loader": "^5.2.7",
"html-webpack-plugin": "^5.5.0",
"less-loader": "^10.2.0", "less-loader": "^10.2.0",
"react-refresh": "^0.11.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"webpack": "^5.64.4", "webpack": "^5.64.4",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",

View file

@ -2,6 +2,7 @@
"apiUrl": "https://api.example.com", "apiUrl": "https://api.example.com",
"imprintUrl": "https://portal.example.com/imprint", "imprintUrl": "https://portal.example.com/imprint",
"privacyPolicyUrl": "https://portal.example.com/privacy", "privacyPolicyUrl": "https://portal.example.com/privacy",
"basename": "/",
"mapTileset": { "mapTileset": {
"url": "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png", "url": "https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png",
"minZoom": 0, "minZoom": 0,

View file

@ -42,9 +42,9 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
api.loadUser() api.loadUser()
}, []) }, [])
return ( return config ? (
<Router basename={process.env.PUBLIC_URL || '/'}> <Router basename={config.basename}>
<Menu fixed="top"> <Menu fixed="top" className={styles.menu}>
<Container> <Container>
<Link to="/" component={MenuItemForLink} header className={styles.pageTitle}> <Link to="/" component={MenuItemForLink} header className={styles.pageTitle}>
OpenBikeSensor OpenBikeSensor
@ -184,7 +184,7 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
</Container> </Container>
</div> </div>
</Router> </Router>
) ) : null
}) })
export default App export default App

View file

@ -1,7 +1,7 @@
@import 'styles.less'; @import 'styles.less';
:global(#root) { :global(#root) {
padding-top: 60px; padding-top: @menuHeight;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -12,6 +12,12 @@
} }
} }
.menu {
> :global(.ui.container) {
height: @menuHeight;
}
}
.footer { .footer {
margin-top: auto; margin-top: auto;
min-height: 12rem; min-height: 12rem;

View file

@ -73,7 +73,7 @@ export default function Stats({user = null}: {user?: null | string}) {
<Statistic.Group widths={2} size="tiny"> <Statistic.Group widths={2} size="tiny">
<Statistic> <Statistic>
<Statistic.Value>{stats ? `${Number(stats?.trackLength / 1000).toFixed(1)} km` : '...'}</Statistic.Value> <Statistic.Value>{stats ? `${Number(stats?.trackLength / 1000).toFixed(1)} km` : '...'}</Statistic.Value>
<Statistic.Label>Total track length</Statistic.Label> <Statistic.Label>Total xtrack length</Statistic.Label>
</Statistic> </Statistic>
<Statistic> <Statistic>
<Statistic.Value>{stats ? formatDuration(stats?.trackDuration) : '...'}</Statistic.Value> <Statistic.Value>{stats ? formatDuration(stats?.trackDuration) : '...'}</Statistic.Value>

8
frontend/src/index.ejs Normal file
View file

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View file

@ -1,5 +1,7 @@
@import 'styles.less';
.mapContainer { .mapContainer {
height: calc(100vh - 60px); height: calc(100vh - @menuHeight);
background: red; background: red;
position: relative; position: relative;
} }

View file

@ -8,3 +8,4 @@
@primaryColor: @obsColorB4; @primaryColor: @obsColorB4;
@secondaryColor: @obsColorG1; @secondaryColor: @obsColorG1;
@menuHeight: 50px;

View file

@ -1,84 +1,161 @@
const path = require('path'); var HtmlWebpackPlugin = require('html-webpack-plugin')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const path = require('path')
const isDevelopment = process.env.NODE_ENV !== 'production'
const API_URL = process.env.API_URL || 'http://localhost:3000'
const BASE_URL = process.env.BASE_URL || isDevelopment ? 'http://localhost:3001' : '__BASE_HREF__'
const PORT = process.env.PORT || 3001
module.exports = { module.exports = {
entry: './src/index.js', entry: './src/index.js',
mode: 'development', mode: isDevelopment ? 'development' : 'production',
cache: isDevelopment && {type: 'filesystem'},
devtool: 'eval-cheap-module-source-map',
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'build'),
filename: 'bundle.js', filename: 'bundle.js',
}, },
cache: false,
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js'], extensions: ['.tsx', '.ts', '.js'],
alias: { alias: {
'mapbox-gl': 'maplibre-gl', 'mapbox-gl': 'maplibre-gl',
'../../theme.config': path.resolve('./src/semantic-ui/theme.config') '../../theme.config': path.resolve('./src/semantic-ui/theme.config'),
}, },
modules: ['node_modules', 'src'], modules: ['node_modules', 'src'],
}, },
plugins: [
new HtmlWebpackPlugin({
base: BASE_URL,
meta: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
'theme-color': '#000000',
description:
'Upload the tracks recorded with your OpenBikeSensor device to this portal and participate in the accumulation of Open Data.',
},
favicon: 'public/favicon.ico',
manifest: 'public/manifest.json',
}),
isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
devServer: {
port: PORT,
hot: true,
compress: true,
historyApiFallback: true,
static: {
directory: path.join(__dirname, 'public'),
serveIndex: true,
},
client: {
webSocketURL: `ws://0.0.0.0:${PORT}/ws`,
},
proxy: {
'/api': API_URL,
'/login': API_URL,
},
},
module: { module: {
rules: [ rules: [
{ {
test: /\.(m?j|t)sx?$/, test: /\.(mjs|js|jsx|ts|tsx)$/,
exclude: /(node_modules|bower_components)/, include: path.resolve('./src'),
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
babelrc: false,
configFile: false,
cacheDirectory: true,
cacheCompression: false,
sourceMaps: true,
inputSourceMap: true,
plugins: [
isDevelopment && 'react-refresh/babel',
[
'@babel/plugin-transform-runtime',
{
corejs: false,
regenerator: true,
useESModules: true,
},
],
].filter(Boolean),
presets: [ presets: [
'@babel/preset-react', [
'@babel/preset-typescript', '@babel/preset-react',
'@babel/preset-env'] {
} development: isDevelopment,
} runtime: 'automatic',
},
],
'@babel/preset-typescript',
'@babel/preset-env',
],
},
},
}, },
{ {
test: /\.css$/i, test: /\.css$/i,
use: [ use: ['style-loader', 'css-loader'],
"style-loader", sideEffects: true,
"css-loader"
],
}, },
{ {
test: /\.module\.less$/i, test: /\.module\.less$/i,
use: [ use: [
// compiles Less to CSS // compiles Less to CSS
"style-loader", 'style-loader',
{loader: "css-loader", options: { modules: true,}}, {loader: 'css-loader', options: {modules: true}},
{loader: "less-loader", options: { {
lessOptions: { loader: 'less-loader',
math: 'always', options: {
} lessOptions: {
}}, math: 'always',
},
},
},
], ],
sideEffects: true,
}, },
{ {
test: /\.less$/i, test: /\.less$/i,
exclude: /\.module\.less$/i, exclude: /\.module\.less$/i,
use: [ use: [
// compiles Less to CSS // compiles Less to CSS
"style-loader", 'style-loader',
"css-loader", 'css-loader',
{loader: "less-loader", options: { {
lessOptions: { loader: 'less-loader',
math: 'always', options: {
} lessOptions: {
}}, math: 'always',
},
},
},
], ],
sideEffects: true,
}, },
{ {
test: /\.(png|svg|jpg|jpeg|gif)$/i, test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource', type: 'asset/resource',
}, },
{ {
test: /\.(woff|woff2|eot|ttf|otf)$/i, test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource', type: 'asset/resource',
},
{
scheme: 'data',
type: 'asset/resource',
}, },
], ],
}, },
}; }