diff --git a/.env b/.env index a947634..256983a 100644 --- a/.env +++ b/.env @@ -1,3 +1,6 @@ REACT_APP_GRAPHQL_URL=https://dev.kartat.hsl.fi/jore/graphql REACT_APP_ROOT_PATH=/ PORT=3000 + +REACT_APP_MAPILLARY_CLIENT_TOKEN=MLY|4594581980627542|90daa882085f792338099ff89cf390b5 + diff --git a/.env.dev b/.env.dev index fc98ce6..6205460 100644 --- a/.env.dev +++ b/.env.dev @@ -1,3 +1,6 @@ REACT_APP_GRAPHQL_URL=https://dev.kartat.hsl.fi/jore/graphql REACT_APP_ROOT_PATH=/kuljettaja PORT=3000 + +REACT_APP_MAPILLARY_CLIENT_TOKEN=MLY|4652630561442946|5b644eb16efdd0e808abf5fd87f82d77 + diff --git a/.env.production b/.env.prod similarity index 52% rename from .env.production rename to .env.prod index e03c69a..ad244e6 100644 --- a/.env.production +++ b/.env.prod @@ -1,3 +1,5 @@ REACT_APP_GRAPHQL_URL=https://kartat.hsl.fi/jore/graphql REACT_APP_ROOT_PATH=/kuljettaja PORT=3000 + +REACT_APP_MAPILLARY_CLIENT_TOKEN=MLY|4843379739113529|30a747d7fcf21f81f293958597d2ae7c diff --git a/.env.stage b/.env.stage index 0bbc9f1..26f0a10 100644 --- a/.env.stage +++ b/.env.stage @@ -1,3 +1,5 @@ REACT_APP_GRAPHQL_URL=https://stage.kartat.hsl.fi/jore/graphql REACT_APP_ROOT_PATH=/kuljettaja PORT=3000 + +REACT_APP_MAPILLARY_CLIENT_TOKEN= diff --git a/.github/workflows/buildAndPublish_dev.yml b/.github/workflows/buildAndPublish_dev.yml new file mode 100644 index 0000000..7556b9f --- /dev/null +++ b/.github/workflows/buildAndPublish_dev.yml @@ -0,0 +1,51 @@ +name: Build and publish Docker Hub dev image + +on: + push: + branches: + - 'development' + +jobs: + build-container: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.KARTAT_DOCKERHUB_USER }} + password: ${{ secrets.KARTAT_DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v3 + with: + images: hsldevcom/hsl-map-web-ui + + - name: Get commit hash + id: commit_hash + run: echo "::set-output name=hash::$(git rev-parse --short "$GITHUB_SHA")" + + - name: Get image timestamp + id: timestamp + run: echo "::set-output name=timestamp::$(date +'%Y-%m-%d')" + + - name: Build and push timestamped tag + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: ${{ format('hsldevcom/hsl-map-web-ui:dev-{0}-{1}', steps.timestamp.outputs.timestamp, steps.commit_hash.outputs.hash) }} + build-args: | + BUILD_ENV=dev + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: hsldevcom/hsl-map-web-ui:dev + build-args: | + BUILD_ENV=dev diff --git a/.github/workflows/buildAndPublish_prod.yml b/.github/workflows/buildAndPublish_prod.yml new file mode 100644 index 0000000..cf80d0e --- /dev/null +++ b/.github/workflows/buildAndPublish_prod.yml @@ -0,0 +1,51 @@ +name: Build and publish Docker Hub prod image + +on: + push: + branches: + - 'master' + +jobs: + build-container: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.KARTAT_DOCKERHUB_USER }} + password: ${{ secrets.KARTAT_DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v3 + with: + images: hsldevcom/hsl-map-web-ui + + - name: Get commit hash + id: commit_hash + run: echo "::set-output name=hash::$(git rev-parse --short "$GITHUB_SHA")" + + - name: Get image timestamp + id: timestamp + run: echo "::set-output name=timestamp::$(date +'%Y-%m-%d')" + + - name: Build and push timestamped tag + uses: docker/build-push-action@v2 + with: + context: . + push: ${{ github.event_name == 'push' }} + tags: ${{ format('hsldevcom/hsl-map-web-ui:prod-{0}-{1}', steps.timestamp.outputs.timestamp, steps.commit_hash.outputs.hash) }} + build-args: | + BUILD_ENV=prod + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: ${{ github.event_name == 'push' }} + tags: hsldevcom/hsl-map-web-ui:prod + build-args: | + BUILD_ENV=prod diff --git a/.github/workflows/buildAndPublish_stage.yml b/.github/workflows/buildAndPublish_stage.yml new file mode 100644 index 0000000..cea07fd --- /dev/null +++ b/.github/workflows/buildAndPublish_stage.yml @@ -0,0 +1,51 @@ +name: Build and publish Docker Hub stage image + +on: + push: + branches: + - 'stage' + +jobs: + build-container: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.KARTAT_DOCKERHUB_USER }} + password: ${{ secrets.KARTAT_DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v3 + with: + images: hsldevcom/hsl-map-web-ui + + - name: Get commit hash + id: commit_hash + run: echo "::set-output name=hash::$(git rev-parse --short "$GITHUB_SHA")" + + - name: Get image timestamp + id: timestamp + run: echo "::set-output name=timestamp::$(date +'%Y-%m-%d')" + + - name: Build and push timestamped tag + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: ${{ format('hsldevcom/hsl-map-web-ui:stage-{0}-{1}', steps.timestamp.outputs.timestamp, steps.commit_hash.outputs.hash) }} + build-args: | + BUILD_ENV=stage + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: hsldevcom/hsl-map-web-ui:stage + build-args: | + BUILD_ENV=stage diff --git a/.github/workflows/buildOnPullRequest.yml b/.github/workflows/buildOnPullRequest.yml new file mode 100644 index 0000000..cb0ad64 --- /dev/null +++ b/.github/workflows/buildOnPullRequest.yml @@ -0,0 +1,21 @@ +name: Build image + +on: + pull_request: + branches-ignore: + - 'master' + +jobs: + build-container: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + tags: hsldevcom/hsl-map-web-ui:dev + build-args: | + BUILD_ENV=dev diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 45e5bc8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,13 +0,0 @@ -sudo: required - -branches: - only: - - development - - stage - - master - -services: docker - -language: C - -script: ./travis-build.sh diff --git a/Dockerfile b/Dockerfile index 49f631d..cd428a8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN yarn # Bundle app source COPY . ${WORK} -ARG BUILD_ENV=production +ARG BUILD_ENV=prod COPY .env.${BUILD_ENV} ${WORK}/.env.production RUN yarn build diff --git a/docker-deploy-all.sh b/docker-deploy-all.sh deleted file mode 100755 index 8e3c1d5..0000000 --- a/docker-deploy-all.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -e - -# Builds and deploys all images for the Azure environments - -ORG=${ORG:-hsldevcom} - -for TAG in dev stage production; do - DOCKER_IMAGE=$ORG/hsl-map-web-ui:${TAG} - - docker build --build-arg BUILD_ENV=${TAG} -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE -done diff --git a/docker-deploy-tag.sh b/docker-deploy-tag.sh deleted file mode 100755 index 07f9df6..0000000 --- a/docker-deploy-tag.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -e - -ORG=${ORG:-hsldevcom} - -read -p "Tag: " TAG - -DOCKER_TAG=${TAG:-production} -DOCKER_IMAGE=$ORG/hsl-map-web-ui:${DOCKER_TAG} - -docker build --build-arg BUILD_ENV=${TAG:-production} -t $DOCKER_IMAGE . -docker push $DOCKER_IMAGE diff --git a/index.ejs b/index.ejs deleted file mode 100644 index f94942d..0000000 --- a/index.ejs +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - HSL Kuljettajaohjeet - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/package.json b/package.json index 1e453ea..0de3fdd 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "homepage": "/kuljettaja", "dependencies": { + "@turf/turf": "^6.5.0", "apollo-cache-inmemory": "^1.6.2", "apollo-client": "^2.6.3", "apollo-fetch": "^0.7.0", @@ -14,8 +15,9 @@ "forever": "^1.0.0", "graphql": "^14.6.0", "graphql-tag": "^2.10.1", - "leaflet": "^1.5.1", + "leaflet": "^1.6.0", "lodash": "^4.17.11", + "mapillary-js": "^4.0.0", "material-ui": "^0.20.2", "mobx": "^5.15.4", "mobx-react": "^6.2.2", diff --git a/serve.json b/serve.json deleted file mode 100644 index 8fb2c57..0000000 --- a/serve.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "port": 3000 -} \ No newline at end of file diff --git a/server.js b/server.js deleted file mode 100644 index 209f512..0000000 --- a/server.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -const webpack = require("webpack"); -const WebpackDevServer = require("webpack-dev-server"); -const config = require("./webpack.config"); - -const PORT = process.env.PORT || 3000; - -new WebpackDevServer(webpack(config), { - publicPath: config.output.publicPath, - hot: true, - historyApiFallback: true, - stats: { - colors: true, - }, -}).listen(PORT, "localhost", (err) => { - if (err) console.log(err); // eslint-disable-line no-console - console.log("Listening at port 3000"); // eslint-disable-line no-console -}); diff --git a/src/components/MapillaryViewer.js b/src/components/MapillaryViewer.js new file mode 100644 index 0000000..4739bca --- /dev/null +++ b/src/components/MapillaryViewer.js @@ -0,0 +1,120 @@ +import React, { useRef, useState, useEffect, useCallback } from "react"; +import { Viewer } from "mapillary-js"; +import { FiXCircle } from "react-icons/fi"; +import { observer } from "mobx-react-lite"; +import { getClosestMapillaryImage } from "../utils/mapUtils"; +import styles from "./mapillaryViewer.module.css"; + +const MapillaryViewer = observer( + ({ location, elementId, onNavigation, className, onCloseViewer }) => { + const [error, setError] = useState(null); + const mly = useRef(null); + const resizeListener = useRef(null); + const prevLocation = useRef(null); + + const createResizeListener = useCallback( + (currentMly) => () => { + if (currentMly) { + currentMly.resize(); + } + }, + [] + ); + + const initMapillary = useCallback(() => { + let currentMly = mly.current; + + if (currentMly) { + return; + } + const accessToken = process.env.REACT_APP_MAPILLARY_CLIENT_TOKEN; + const viewerOptions = { + accessToken, + container: elementId, + render: { cover: false }, + imageKey: "2143821709111283", + }; + currentMly = new Viewer(viewerOptions); + + const currentResizeListener = createResizeListener(currentMly); + + if (resizeListener.current) { + window.removeEventListener("resize", resizeListener.current); + } + + window.addEventListener("resize", currentResizeListener); + resizeListener.current = currentResizeListener; + + currentMly.setFilter(["==", "organizationKey", "227572519135262"]); + currentMly.on("image", (evt) => onNavigation(evt.image.lngLat)); + mly.current = currentMly; + }, [mly.current, resizeListener.current]); + + const showLocation = useCallback( + async (location) => { + if (mly.current) { + try { + const closest = await getClosestMapillaryImage({ + lat: location.lat, + lng: location.lng, + }); + if (closest && closest.id) { + mly.current + .moveTo(closest.id) + .then((node) => { + onNavigation(node.lngLat); + }) + .catch((error) => console.warn(error)); + setError(null); + } else { + setError("Katukuvia ei löytynyt."); + } + } catch (e) { + setError("Katunäkymän haku epäonnistui."); + } + } + }, + [mly.current, mly.current && mly.current.isNavigable] + ); + + // Clean up separately from other effects + useEffect(() => { + if (!mly.current) { + initMapillary(); + } + + return () => { + mly.current = null; + window.removeEventListener("resize", resizeListener.current); + }; + }, []); + + const locationEquals = (location, prevLocation) => { + if (!prevLocation) { + return false; + } + return location.lat === prevLocation.lat && location.lng === prevLocation.lng; + }; + + useEffect(() => { + if ( + location && + (!prevLocation.current || !locationEquals(location, prevLocation.current)) + ) { + showLocation(location); + prevLocation.current = location; + } + }, [location, prevLocation.current, showLocation]); + return ( +
+ {error &&
{error}
} +
+
+ +
+
+ ); + } +); + +export default MapillaryViewer; diff --git a/src/components/app.module.css b/src/components/app.module.css index 6f92f82..5462157 100644 --- a/src/components/app.module.css +++ b/src/components/app.module.css @@ -1,3 +1,5 @@ .root { background-color: var(--backgroundLightGray); + -webkit-tap-highlight-color: transparent; + overflow-y: hidden; } diff --git a/src/components/expandButton.js b/src/components/expandButton.js index aa79e06..00ea642 100644 --- a/src/components/expandButton.js +++ b/src/components/expandButton.js @@ -10,7 +10,7 @@ const ExpandButton = ({ onClick, labelText, isExpanded }) => ( [styles.expanded]: isExpanded, })} onClick={onClick}> - +

{labelText}

); diff --git a/src/components/expandButton.module.css b/src/components/expandButton.module.css index dc7c154..8164639 100644 --- a/src/components/expandButton.module.css +++ b/src/components/expandButton.module.css @@ -12,6 +12,7 @@ border-top-right-radius: 4px; border-bottom: 1px solid #ccc; box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); + cursor: pointer; } .expandButton p { diff --git a/src/components/header.js b/src/components/header.js index ce7a504..09cd287 100644 --- a/src/components/header.js +++ b/src/components/header.js @@ -1,16 +1,23 @@ import React from "react"; import { Link } from "react-router-dom"; +import classnames from "classnames"; import styles from "./header.module.css"; import hslLogo from "../icons/hsl-logo.png"; -const Header = () => ( -
+const Header = (props) => ( +
-
+
HSL / HRT
-

Kuljettajaohjeet

+

+ Kuljettajaohjeet +

); diff --git a/src/components/header.module.css b/src/components/header.module.css index 34fc7d3..57f6264 100644 --- a/src/components/header.module.css +++ b/src/components/header.module.css @@ -1,8 +1,20 @@ .root { - height: 150px; + height: auto; padding: var(--contentContainerPadding); background-color: var(--busBlue); color: white; + z-index: 10; +} + +.rootMobile { + display: flex; + padding: 10px; + height: 35px; +} + +.logoMobile img { + height: 30px; + display: flex; } .logo img { @@ -12,3 +24,11 @@ .sectionTitle { color: white; } + +.sectionTitleMobile { + display: flex; + font-size: 1.3em; + margin: 0px; + margin-left: 20px; + align-items: center; +} diff --git a/src/components/home.js b/src/components/home.js index adb80b6..63f00e9 100644 --- a/src/components/home.js +++ b/src/components/home.js @@ -2,6 +2,7 @@ import React from "react"; import classnames from "classnames"; import Header from "./header"; import styles from "./home.module.css"; +import { isMobile } from "../utils/browser"; import { inject, observer } from "mobx-react"; import LineList from "./lineList"; @@ -58,15 +59,20 @@ class Home extends React.Component { return (
-
- +
+
-
+
0 ? null : styles.disabled + selectedLines.length > 0 ? null : styles.disabled, + isMobile ? styles.buttonMobile : null )}> Siirry karttanäkymään
diff --git a/src/components/home.module.css b/src/components/home.module.css index 2fd3280..ed2cf73 100644 --- a/src/components/home.module.css +++ b/src/components/home.module.css @@ -2,6 +2,10 @@ padding: var(--contentContainerPadding); } +.rootMobile { + overflow: hidden; +} + .button { color: #fff; text-align: center; @@ -17,6 +21,11 @@ width: 350px; transition: 0.2s; margin-right: 15px; + border-radius: 5px; +} + +.buttonMobile { + height: 40px; } .disabled { @@ -41,6 +50,10 @@ border-top: 1px solid #dedede; } +.buttonContainerMobile { + flex-direction: column; +} + .divider { margin: 10px; border-bottom: 1px solid #dedede; diff --git a/src/components/lineList.js b/src/components/lineList.js index f2feb8c..aa381a1 100644 --- a/src/components/lineList.js +++ b/src/components/lineList.js @@ -169,7 +169,11 @@ const LineList = inject("lineStore")( .map((line, index) => (
handleClick(line)}> diff --git a/src/components/lineList.module.css b/src/components/lineList.module.css index c715450..9ce767d 100644 --- a/src/components/lineList.module.css +++ b/src/components/lineList.module.css @@ -6,6 +6,12 @@ border-radius: 5px; } +.divContainerMobile { + margin: 5px; + padding: 3px 3px 3px 7px; + border-radius: 5px; +} + .loading { padding-top: 10vh; } diff --git a/src/components/map.js b/src/components/map.js index b84fafb..714171a 100644 --- a/src/components/map.js +++ b/src/components/map.js @@ -1,11 +1,14 @@ import React from "react"; +import Header from "./header"; import Sidebar from "./sidebar"; import MapLeaflet from "./mapLeaflet"; +import classnames from "classnames"; import styles from "./map.module.css"; +import { isMobile } from "../utils/browser"; const COLORS = [ "66B2FF", - "FF3333", + "ff6633", "10864B", "CDB100", "1D80B2", @@ -13,7 +16,6 @@ const COLORS = [ "F7922D", "DC00DC", ]; - class Map extends React.Component { constructor() { super(); @@ -23,13 +25,32 @@ class Map extends React.Component { showFilterFullScreen: false, isFullScreen: false, center: null, + bottomsheetState: { context: { mapBottomPadding: 0, buttonBottomPadding: 0 } }, + isMobile: false, + showPrintLayout: false, }; this.mapLeafletToggleFullscreen = this.mapLeafletToggleFullscreen.bind(this); this.routeFilterToggleFilter = this.routeFilterToggleFilter.bind(this); this.routeFilterItemToggleChecked = this.routeFilterItemToggleChecked.bind(this); this.setMapCenter = this.setMapCenter.bind(this); + this.setDrawerHeight = this.setDrawerHeight.bind(this); + } + + componentDidMount() { + window.addEventListener("resize", this.resizeHandler); + this.setState({ isMobile: isMobile }); + this.togglePrintLayout = this.togglePrintLayout.bind(this); } + componentWillUnmount() { + window.removeEventListener("resize", this.resizeHandler); + } + + resizeHandler = () => { + const mobile = window.innerWidth < 700; + this.setState({ isMobile: mobile }); + }; + setMapCenter(center) { this.setState({ center }); } @@ -86,12 +107,22 @@ class Map extends React.Component { return routes; } + setDrawerHeight = (height) => { + if (this.refs.drawer.scrollTop < height) { + this.refs.drawer.scrollTop = height; + } + }; + togglePrintLayout() { + this.setState({ showPrintLayout: !this.state.showPrintLayout }); + this.setState(); + } + render() { const lines = this.props.mapProps.map((props) => { const routes = props.lineRoutes.map((r) => ({ - ...r, - name: props.nameFi, - id: `${props.nameFi}_${r.routeId}_${r.direction}_${r.dateBegin}_${r.dateEnd}` + ...r, + name: props.nameFi, + id: `${props.nameFi}_${r.routeId}_${r.direction}_${r.dateBegin}_${r.dateEnd}`, })); return { lineId: props.lineId, @@ -103,32 +134,71 @@ class Map extends React.Component { notes: props.notes, }; }); - - const routes = lines.reduce(( acc, curr ) => acc.concat(curr.routes), []) + const routes = lines.reduce((acc, curr) => acc.concat(curr.routes), []); const coloredRoutes = this.coloredRoutes(routes); + + const mapComponent = ( + + ); + return (
- - + {this.state.isMobile &&
} + {this.state.isMobile && ( +
+
+
+
+
+ +
+
+
+ )} + {!this.state.isMobile && ( + + )} + {this.state.isMobile ? ( +
{mapComponent}
+ ) : ( + mapComponent + )}
); } diff --git a/src/components/map.module.css b/src/components/map.module.css index bdc4fae..744b819 100644 --- a/src/components/map.module.css +++ b/src/components/map.module.css @@ -8,3 +8,50 @@ flex-direction: row; } } + +.drawerContainer { + background-color: #eef1f3; + overflow-y: scroll; + height: 100%; + position: absolute; + width: 100%; + scroll-behavior: smooth; +} + +.drawerPadding { + height: 99%; + width: 0; +} + +.drawerContent { + z-index: 5; + min-height: 55vh; + background: white; + position: relative; + background-color: #eef1f3; +} + +.dragLine { + width: 30px; + height: 4px; + border-radius: 2px; + background-color: #cccccc; + display: block; + z-index: 2; + margin: auto; + position: relative; + top: -65px; +} +.contentContainer { + position: relative; + transition: 0.5s; + top: -80px; + box-shadow: 0 -10px 10px -10px rgba(0, 0, 0, 0.3); + border-radius: 15px 15px 0 0; + padding-top: 15px; + background-color: #eef1f3; +} + +.mapContainer { + z-index: 1; +} diff --git a/src/components/mapContainer.js b/src/components/mapContainer.js index 6722c04..d74b6b9 100644 --- a/src/components/mapContainer.js +++ b/src/components/mapContainer.js @@ -80,6 +80,7 @@ const restroomQuery = ` lon point dateImported + mode } } } @@ -122,9 +123,16 @@ class MapContainer extends Component { const restrooms = await fetch({ query: restroomQuery, }); - return restrooms.data && restrooms.data.allRestrooms - ? restrooms.data.allRestrooms.edges - : []; + + const restroomsData = + restrooms.data && restrooms.data.allRestrooms + ? restrooms.data.allRestrooms.edges + : []; + + const restroomsInUse = restroomsData.filter( + (restroom) => restroom.node.mode === "käytössä" + ); + return restroomsInUse; }; lineObject = (param, params, index) => { diff --git a/src/components/mapLeaflet.js b/src/components/mapLeaflet.js index f4f9ea4..0869a45 100644 --- a/src/components/mapLeaflet.js +++ b/src/components/mapLeaflet.js @@ -4,6 +4,12 @@ import L from "leaflet"; import { first, last } from "lodash"; import "leaflet/dist/leaflet.css"; import { mapIcon, stopIcon } from "../utils/mapIcon"; +import { + closestPointCompareReducer, + closestPointInGeometry, +} from "../utils/closestPoint"; +import { circleMarker } from "leaflet"; +import "mapillary-js/dist/mapillary.css"; import startIcon1 from "../icons/icon-suunta1.svg"; import startIcon2 from "../icons/icon-suunta2.svg"; import timeIcon1 from "../icons/icon-time1.svg"; @@ -15,6 +21,8 @@ import fullScreenEnterIcon from "../icons/icon-fullscreen-enter.svg"; import fullScreenExitIcon from "../icons/icon-fullscreen-exit.svg"; import restroomIcon from "../icons/restroom-solid.svg"; import styles from "./mapLeaflet.module.css"; +import MapillaryViewer from "./MapillaryViewer.js"; +import { isMobile } from "../utils/browser"; const MAX_DISTANCE_TO_RESTROOM = 500; @@ -47,18 +55,19 @@ const addMarkersToLayer = (stops, direction, map, restrooms) => { const firstStopMarkerLatLng = L.marker([firstStop.lat, firstStop.lon]).getLatLng(); const lastStopMarkerLatLng = L.marker([lastStop.lat, lastStop.lon]).getLatLng(); const closeByRestrooms = []; - restrooms.forEach((restroom) => { - const restroomMarker = L.marker([restroom.node.lat, restroom.node.lon]).getLatLng(); - const distanceFromFirstStop = firstStopMarkerLatLng.distanceTo(restroomMarker); - const distanceFromLastStop = lastStopMarkerLatLng.distanceTo(restroomMarker); - if ( - distanceFromFirstStop < MAX_DISTANCE_TO_RESTROOM || - distanceFromLastStop < MAX_DISTANCE_TO_RESTROOM - ) { - closeByRestrooms.push(restroom.node); - } - }); - + if (restrooms) { + restrooms.forEach((restroom) => { + const restroomMarker = L.marker([restroom.node.lat, restroom.node.lon]).getLatLng(); + const distanceFromFirstStop = firstStopMarkerLatLng.distanceTo(restroomMarker); + const distanceFromLastStop = lastStopMarkerLatLng.distanceTo(restroomMarker); + if ( + distanceFromFirstStop < MAX_DISTANCE_TO_RESTROOM || + distanceFromLastStop < MAX_DISTANCE_TO_RESTROOM + ) { + closeByRestrooms.push(restroom.node); + } + }); + } closeByRestrooms.forEach((closeByRestroom) => { let icon = mapIcon(restroomIcon); const markerr = L.marker([closeByRestroom.lat, closeByRestroom.lon], { icon }); @@ -101,7 +110,7 @@ const addGeometryLayer = (geometries, map) => { }); }; -const addControlButton = (map, toggleFullscreen) => { +const addControlButton = (map, toggleFullscreen, resetMapillaryLocation) => { const FullScreenControl = L.Control.extend({ options: { position: "topleft", @@ -116,6 +125,7 @@ const addControlButton = (map, toggleFullscreen) => { container.appendChild(icon); container.onclick = () => { icon.src = toggleFullscreen() ? fullScreenExitIcon : fullScreenEnterIcon; + resetMapillaryLocation(); }; L.DomEvent.disableClickPropagation(container); return container; @@ -147,15 +157,41 @@ const addLocationButton = (map, toggleLocation) => { map.addControl(new LocationControl()); }; +const addMapillaryButton = (map, initMapillaryLayer) => { + const MapillaryControl = L.Control.extend({ + options: { + position: "topleft", + }, + onAdd: () => { + const icon = L.DomUtil.create("div"); + const container = L.DomUtil.create("button", "leaflet-bar leaflet-control"); + icon.height = "11"; + icon.width = "11"; + icon.innerHTML = "M"; + icon.style.fontSize = "15px"; + icon.style.fontWeight = "500"; + container.className = styles.controlButton; + icon.style.color = isMobile ? "black" : "rgb(5, 203, 99)"; + container.appendChild(icon); + container.onclick = () => { + icon.style.color = initMapillaryLayer() ? "rgb(5, 203, 99)" : "black"; + }; + L.DomEvent.disableClickPropagation(container); + return container; + }, + }); + map.addControl(new MapillaryControl()); +}; + const addRouteFilterLayer = (map) => { const RouteFilterControl = L.Control.extend({ options: { - position: "bottomright", + position: "topright", }, onAdd: () => { const container = L.DomUtil.create( "div", - "leaflet-bar leaflet-control leaflet-control-bottomright " + styles.filterArea + "leaflet-bar leaflet-control leaflet-control-topright " + styles.filterArea ); L.DomEvent.disableScrollPropagation(container); L.DomEvent.disableClickPropagation(container); @@ -165,11 +201,18 @@ const addRouteFilterLayer = (map) => { map.addControl(new RouteFilterControl()); }; -const updateFilterLayer = (isFullScreen) => { +const updateFilterLayer = (isFullScreen, isRouteFilterExpanded, mapillaryLocation) => { // This function moves all routeFilters from sidebar to map and vice versa + const routeFilter = document.getElementsByClassName("leaflet-control-topright")[0]; + if (isFullScreen) { + if (isRouteFilterExpanded && mapillaryLocation) { + routeFilter.style.height = "45vh"; + } else { + routeFilter.style.height = null; + } for (const node of document.querySelectorAll(".route-filter")) { - document.getElementsByClassName("leaflet-control-bottomright")[0].appendChild(node); + document.getElementsByClassName("leaflet-control-topright")[0].appendChild(node); } } else { for (const node of document.querySelectorAll(".route-filter")) { @@ -186,6 +229,9 @@ class MapLeaflet extends React.Component { this.state = { locationOn: false, locationMarker: null, + showMapillaryLayer: !isMobile, + mapillaryLocation: null, + mapillaryImageLocation: null, }; this.map = null; @@ -193,18 +239,31 @@ class MapLeaflet extends React.Component { this.initializeMap = this.initializeMap.bind(this); this.addLayersToMap = this.addLayersToMap.bind(this); this.toggleLocation = this.toggleLocation.bind(this); + this.initMapillaryLayer = this.initMapillaryLayer.bind(this); + this.bindEvents = this.bindEvents.bind(this); + this.onClick = this.onClick.bind(this); + this.setMapillaryLocation = this.setMapillaryLocation.bind(this); } componentDidMount() { this.initializeMap(); + this.bindEvents(); } componentDidUpdate(prevProps, prevState) { // All layers except the base layer are removed when the component is updated this.map.eachLayer((layer) => { - if (!layer.options.baseLayer) this.map.removeLayer(layer); + if (!layer.options.baseLayer) { + if ( + layer.options.type !== "mapillaryGeoJsonLayer" && + layer.options.type !== "mapillaryImageMarker" && + layer.options.type !== "mapillaryHighlightMarker" && + !layer._layers + ) { + this.map.removeLayer(layer); + } + } }); - // Leaflet map is updated once geometry and stop data has been fetched // The view (bounding box) is set only the first time the route stops are recieved if ( @@ -236,10 +295,56 @@ class MapLeaflet extends React.Component { this.state.locationMarker.addTo(this.map); } - updateFilterLayer(this.props.isFullScreen); + if (this.props.selectedRoutes.length !== prevProps.selectedRoutes.length) { + this.resetMapillaryLocation(); + } + + updateFilterLayer( + this.props.isFullScreen, + this.props.isRouteFilterExpanded, + this.state.mapillaryLocation + ); this.map.invalidateSize(); } + bindEvents = () => { + if (!this.map || this.eventsEnabled) { + return; + } + + this.map.on("click", this.onClick); + this.map.on("mousemove", this.onHover); + this.eventsEnabled = true; + }; + + unbindEvents = () => { + if (!this.map || !this.eventsEnabled) { + return; + } + + this.map.off("click", this.onMapClick); + this.map.off("mousemove", this.onHover); + this.eventsEnabled = false; + }; + + removeMarker = () => { + if (this.marker) { + this.marker.remove(); + this.marker = null; + } + }; + + componentWillUnmount() { + this.unbindEvents(); + this.removeMarker(); + } + + initMapillaryLayer() { + const showMapillaryLayer = this.state.showMapillaryLayer; + this.setState({ showMapillaryLayer: !showMapillaryLayer }); + return this.state.showMapillaryLayer; + } + toggleLocation() { const nextLocationOn = !this.state.locationOn; @@ -275,25 +380,29 @@ class MapLeaflet extends React.Component { } initializeMap() { - const baseMapOptions = { - maxZoom: 18, - tileSize: 512, - zoomOffset: -1, - attribution: - 'Map data © OpenStreetMap contributors, ' + - 'CC-BY-SA, ' + - 'Imagery © Mapbox', - retina: L.retina ? "@2x" : "", - baseLayer: true, - }; const digitransitTileLayer = L.tileLayer( - "https://digitransit-prod-cdn-origin.azureedge.net/map/v1/hsl-map/{z}/{x}/{y}{retina}.png", - baseMapOptions + "https://cdn.digitransit.fi/map/v2/hsl-map/{z}/{x}/{y}{retina}.png", + { + maxZoom: 18, + tileSize: 512, + zoomOffset: -1, + attribution: + 'Map data © OpenStreetMap contributors, ' + + 'CC-BY-SA, ' + + 'Imagery © Mapbox', + retina: L.Browser.retina ? "@2x" : "", // Use @2x tiles for retina displays + baseLayer: true, + } ); const aerialTileLayer = L.tileLayer( - "https://ortophotos.blob.core.windows.net/hsy-map/hsy_tiles2/{z}/{x}/{y}{retina}.jpg", - baseMapOptions + "https://ortophotos.blob.core.windows.net/hsy-map/hsy_tiles2/{z}/{x}/{y}.jpg", + { + maxZoom: 18, + tileSize: 256, + attribution: 'Imagery © HSY', + detectRetina: true, // @2x tiles not available, use detectRetina -feature + } ); this.map = L.map("map-leaflet", { @@ -308,12 +417,19 @@ class MapLeaflet extends React.Component { }; L.control.layers(baseMaps).addTo(this.map); - addControlButton(this.map, this.props.toggleFullscreen); + if (!isMobile) { + addControlButton( + this.map, + this.props.toggleFullscreen, + this.resetMapillaryLocation + ); + } addLocationButton(this.map, this.toggleLocation); + addMapillaryButton(this.map, this.initMapillaryLayer); addRouteFilterLayer(this.map); } - addLayersToMap() { + async addLayersToMap() { if (this.props.routes) { const selectedStops = this.props.routes.filter((route) => this.props.selectedRoutes.includes( @@ -340,15 +456,137 @@ class MapLeaflet extends React.Component { } } + onClick(e) { + if (!this.state.showMapillaryLayer) { + return; + } + for (const key in e.target._layers) { + const layer = e.target._layers[key]; + if (layer) { + if (layer.options.type === "mapillaryHighlightMarker") { + this.setState({ mapillaryLocation: layer._latlng }); + } + } + } + } + + setMapillaryLocation(position, playService) { + if (!this.state.mapillaryLocation) { + if (playService) { + playService.stop(); + } + return; + } + if (this.imageMarker) { + this.imageMarker.remove(); + this.imageMarker = null; + } + if (this.map && position) { + const marker = this.createMarker({ + position: position, + opacity: 1, + type: "mapillaryImageMarker", + color: "red", + }); + if (!this.imageMarker) { + this.imageMarker = marker; + this.imageMarker.addTo(this.map); + } else { + this.imageMarker.setLatLng(position); + } + } else if (!position) { + this.removeMarker(); + } + this.setState({ mapillaryImageLocation: { lat: position.lat, lng: position.lon } }); + } + + onHover = (e) => { + if (!this.state.showMapillaryLayer) { + return; + } + const { latlng } = e; + const features = []; + this.map.eachLayer((layer) => { + if (layer.feature && layer.feature.geometry) { + features.push(layer.feature); + } + }); + if (!features.length) { + return; + } + + // Get the feature closest to where the user is hovering + let featurePoint = closestPointCompareReducer( + features, + (feature) => closestPointInGeometry(latlng, feature.geometry, 200), + latlng + ); + this.highlightedLocation = featurePoint; + + featurePoint = featurePoint && !featurePoint.equals(latlng) ? featurePoint : false; + this.highlightMapillaryPoint(featurePoint); + }; + + createMarker = (options) => { + return circleMarker(options.position, { + type: options.type, + radius: 4, + color: options.color, + opacity: options.opacity, + }); + }; + + highlightMapillaryPoint = (position) => { + if (this.mapillaryHighlightMarker) { + this.mapillaryHighlightMarker.remove(); + this.marker = null; + } + + if (this.map && position) { + const marker = this.createMarker({ + position: position, + opacity: 0.25, + type: "mapillaryHighlightMarker", + color: "red", + }); + this.mapillaryHighlightMarker = marker; + this.mapillaryHighlightMarker.addTo(this.map); + } else if (!position) { + this.removeMarker(); + } + }; + + resetMapillaryLocation = () => { + this.map.eachLayer((layer) => { + if (!layer.options.baseLayer) { + this.map.removeLayer(layer); + } + }); + this.setState({ mapillaryLocation: null }); + }; + render() { - // Container div (id="map-leaflet") for leaflet map is created return (
+ className={classNames(styles.container, { + [styles.containerMobile]: isMobile, + })}> +
+ {this.state.mapillaryLocation && ( + + )} +
); } } diff --git a/src/components/mapLeaflet.module.css b/src/components/mapLeaflet.module.css index 77a6a16..92a7b7f 100644 --- a/src/components/mapLeaflet.module.css +++ b/src/components/mapLeaflet.module.css @@ -1,8 +1,19 @@ .root { width: 100%; - min-height: 400px; - height: 100vh; + height: 100%; overflow: hidden; + flex: 1; +} + +.container { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; +} + +.containerMobile { + height: calc(100vh - 55px); } @media (max-width: 767px) { @@ -30,7 +41,7 @@ } .fullScreen { - position: fixed !important; + position: relative; left: 0; top: 0; width: 100%; @@ -47,6 +58,16 @@ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); } +.controlbuttontest { + background-color: red; + width: 26px; + height: 26px; + border: none; + border-radius: 4px; + cursor: pointer; + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); +} + .controlButton img { padding-top: 4px; } @@ -60,3 +81,8 @@ overflow-y: auto; -webkit-overflow-scrolling: touch; } + +.printableMap { + max-width: 750px; + max-height: 750px; +} diff --git a/src/components/mapillaryViewer.module.css b/src/components/mapillaryViewer.module.css new file mode 100644 index 0000000..6aa79d1 --- /dev/null +++ b/src/components/mapillaryViewer.module.css @@ -0,0 +1,52 @@ +.viewerWrapper { + display: flex; + width: 100%; + flex: 1; + color: white; +} + +.mapillaryElement { + width: 100%; + height: 100%; + color: white; +} + +div[class~="mapillary-attribution-username"] { + color: white; +} + +div[class~="mapillary-attribution-date"] { + color: white; +} + +.errorMessage { + position: absolute; + color: red; + z-index: 99; + padding: 3px; +} + +.closeButton { + position: absolute; + right: 0px; + padding: 3px; + cursor: pointer; +} + +.mapillary-js-canvas { + width: 100%; +} + +.closeButtonIcon { + height: 30px; + width: 30px; + fill: #66b2ff; + transition: 0.2s; +} + +.closeButtonIcon:hover { + height: 30px; + width: 30px; + fill: #9bccfe; + transition: 0.2s; +} diff --git a/src/components/notes.module.css b/src/components/notes.module.css index 948134c..7e61271 100644 --- a/src/components/notes.module.css +++ b/src/components/notes.module.css @@ -1,3 +1,10 @@ .note { font-size: 12px; } + +@media print { + .note { + visibility: hidden; + height: 0px; + } +} diff --git a/src/components/routeFilter.js b/src/components/routeFilter.js index 80118f7..86079e2 100644 --- a/src/components/routeFilter.js +++ b/src/components/routeFilter.js @@ -13,7 +13,7 @@ const parseDate = (date) => { const RouteFilter = (props) => (
( /> ) : null}
- {Object.values(groupBy(props.routes, "dateBegin")).map((routeDate, dateIndex) => ( -
-

- {parseDate(routeDate[0].dateBegin)} - {parseDate(routeDate[0].dateEnd)} -

-
- {routeDate.map((route, routeIndex) => { - return ( - ({ - ...node.stop, - duration: node.duration, - stopIndex: node.stopIndex, - })) - .sort((a, b) => a.stopIndex - b.stopIndex)} - transportType={props.transportType} - isFullScreen={props.isFullScreen} - isChecked={props.selectedRoutes.includes(route.id)} - onChange={props.toggleChecked} - setMapCenter={props.setMapCenter} - color={route.color} - /> - ); - })} + {Object.values(groupBy(props.routes, "dateBegin")).map((routeDate, dateIndex) => { + const hasSelectedRoutes = routeDate.find((route) => { + return props.selectedRoutes.includes(route.id); + }); + return ( +
+

+ {parseDate(routeDate[0].dateBegin)} - {parseDate(routeDate[0].dateEnd)} +

+
+ {routeDate.map((route, routeIndex) => { + return ( + ({ + ...node.stop, + duration: node.duration, + stopIndex: node.stopIndex, + timingStopType: node.timingStopType, + })) + .sort((a, b) => a.stopIndex - b.stopIndex)} + transportType={props.transportType} + isFullScreen={props.isFullScreen} + isChecked={props.selectedRoutes.includes(route.id)} + onChange={props.toggleChecked} + setMapCenter={props.setMapCenter} + color={route.color} + /> + ); + })} +
-
- ))} + ); + })}
); diff --git a/src/components/routeFilter.module.css b/src/components/routeFilter.module.css index 0982acc..2b98ce5 100644 --- a/src/components/routeFilter.module.css +++ b/src/components/routeFilter.module.css @@ -33,3 +33,14 @@ .hidden { display: none; } + +@media print { + .root { + border: none; + font-size: 8px; + } + + .noPrint { + display: none; + } +} diff --git a/src/components/routeFilterItem.js b/src/components/routeFilterItem.js index 261d251..c9f9505 100644 --- a/src/components/routeFilterItem.js +++ b/src/components/routeFilterItem.js @@ -34,7 +34,10 @@ class RouteFilterItem extends React.Component { return (
-
+
); diff --git a/src/components/stop.module.css b/src/components/stop.module.css index 03de03b..1019194 100644 --- a/src/components/stop.module.css +++ b/src/components/stop.module.css @@ -18,6 +18,7 @@ font-size: var(--mainTextSize); font-weight: 600; width: 68px; + flex-wrap: wrap; } .textPrimary { @@ -39,6 +40,12 @@ font-size: var(--helpTextSize); } +.timingStopIcon { + width: 20px; + margin-left: 10px; + float: right; +} + @media (min-width: 400px) { .textDuration { position: absolute; @@ -56,3 +63,16 @@ position: static; } } + +@media print { + .root { + margin: 0px; + padding: 3px; + border-bottom: 1px solid gray; + } + + .timingStopIcon { + width: 10px; + padding-top: 2px; + } +} diff --git a/src/components/stopList.js b/src/components/stopList.js index 76ee10b..bc88e15 100644 --- a/src/components/stopList.js +++ b/src/components/stopList.js @@ -10,6 +10,7 @@ const renderStops = (routeStops, isFullScreen, setMapCenter) => stopNameFi={stop.nameFi} stopNameSv={stop.nameSe} duration={stop.duration} + timingStopType={stop.timingStopType} isFullScreen={isFullScreen} onClick={() => setMapCenter({ lat: stop.lat, lng: stop.lon, stopId: stop.stopId })} /> diff --git a/src/components/stopList.module.css b/src/components/stopList.module.css index a571881..2021277 100644 --- a/src/components/stopList.module.css +++ b/src/components/stopList.module.css @@ -5,3 +5,10 @@ border: 1px solid rgb(180, 180, 180); background-color: white; } + +@media print { + .root { + border: none; + margin: 0px; + } +} diff --git a/src/icons/restroom-solid.svg b/src/icons/restroom-solid.svg index 6e3e49b..166407d 100644 --- a/src/icons/restroom-solid.svg +++ b/src/icons/restroom-solid.svg @@ -1 +1,32 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/styles/common.css b/src/styles/common.css index 48743b5..dcd8eaa 100644 --- a/src/styles/common.css +++ b/src/styles/common.css @@ -25,3 +25,10 @@ button.noStyle { padding: 0 0 0 0; cursor: pointer; } + +@media print { + :root { + --mainTextSize: 10px; + --helpTextSize: 6px; + } +} diff --git a/src/utils/browser.js b/src/utils/browser.js new file mode 100644 index 0000000..2e4b672 --- /dev/null +++ b/src/utils/browser.js @@ -0,0 +1 @@ +export const isMobile = window.innerWidth < 700; diff --git a/src/utils/closestPoint.js b/src/utils/closestPoint.js new file mode 100644 index 0000000..58bec22 --- /dev/null +++ b/src/utils/closestPoint.js @@ -0,0 +1,49 @@ +import { latLng } from "leaflet"; + +export function closestPointInGeometry(queryPoint, geometry, maxDistance = 100) { + const queryLatLng = latLng(queryPoint); + + if (geometry.type === "LineString") { + return closestPointOnLine(queryLatLng, geometry.coordinates, maxDistance); + } else if (geometry.type === "MultiLineString") { + return closestPointCompareReducer( + geometry.coordinates, + (coordinate) => closestPointOnLine(queryLatLng, coordinate, maxDistance), + queryLatLng + ); + } + + return false; +} + +export function closestPointCompareReducer(collection, getCandidate, latlng) { + return collection.reduce((current, item) => { + const pointCandidate = getCandidate(item); + + if ( + !current || + (!!pointCandidate && latlng.distanceTo(pointCandidate) < latlng.distanceTo(current)) + ) { + return pointCandidate; + } + + return current; + }, false); +} + +function closestPointOnLine(queryPoint, lineGeometry, maxDistance = 100) { + let prevDistance = maxDistance; + let closestPoint = false; + + for (let i = 0; i < lineGeometry.length; i++) { + const [lng, lat] = lineGeometry[i]; + const distanceFromQuery = queryPoint.distanceTo([lat, lng]); + + if (distanceFromQuery < prevDistance) { + prevDistance = distanceFromQuery; + closestPoint = latLng([lat, lng]); + } + } + + return closestPoint; +} diff --git a/src/utils/mapUtils.js b/src/utils/mapUtils.js new file mode 100644 index 0000000..9f5e159 --- /dev/null +++ b/src/utils/mapUtils.js @@ -0,0 +1,69 @@ +import * as turf from "@turf/turf"; + +const wait = async (delay) => { + return new Promise((resolve) => setTimeout(resolve, delay)); +}; + +const fetchRetry = async (url, delay, tries, fetchOptions = {}) => { + const triesLeft = tries - 1; + if (!triesLeft) { + throw "Mapillary image fetch failed"; + } + + const res = await fetch(url, fetchOptions); + if (!res.ok) { + await wait(delay); + return await fetchRetry(url, delay, triesLeft, fetchOptions); + } + return res; +}; + +export async function getClosestMapillaryImage({ lat, lng }) { + const p = turf.point([lng, lat]); + const buffer = turf.buffer(p, 0.05, { units: "kilometers" }); + const bbox = turf.bbox(buffer); + + const url = `https://graph.mapillary.com/images?fields=id,geometry&bbox=${bbox}&limit=100`; + const delay = 500; + const tries = 3; + + const fetchOptions = { + method: "GET", + contentType: "application/json", + headers: { + Authorization: `Bearer ${process.env.REACT_APP_MAPILLARY_CLIENT_TOKEN}`, + }, + }; + + let authResponse = await fetchRetry( + `${url}&organization_id=227572519135262`, + delay, + tries, + fetchOptions + ); + let json = await authResponse.json(); + + //If we get no images with organization_id then try fetching without it + if (json.data.length === 0) { + const res = await fetch(url, fetchOptions); + json = await res.json(); + } + + if (!json.data) { + return null; + } + let closest; + json.data.forEach((feature) => { + const coordinates = feature.geometry.coordinates; + let distance = Math.hypot( + Math.abs(lat - coordinates[1]), + Math.abs(lng - coordinates[0]) + ); + if (!closest || distance < closest.distance) { + closest = feature; + closest.distance = distance; + } + }); + + return closest; +} diff --git a/travis-build.sh b/travis-build.sh deleted file mode 100755 index 29a556f..0000000 --- a/travis-build.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -e - -ORG=${ORG:-hsldevcom} -DOCKER_TAG=${TRAVIS_BUILD_NUMBER:-latest} -BUILD_AND_PUSH_BRANCHES=('development', 'stage', 'master') - -if [[ $TRAVIS_BRANCH == "development" ]]; then - DOCKER_TAG=dev -fi - -if [[ $TRAVIS_BRANCH == "stage" ]]; then - DOCKER_TAG=stage -fi - -if [[ $TRAVIS_BRANCH == "master" ]]; then - DOCKER_TAG=production -fi - -DOCKER_IMAGE=$ORG/hsl-map-web-ui:${DOCKER_TAG} - -echo "Building image with tag -> ${DOCKER_TAG}" - -docker build --tag=$DOCKER_IMAGE . - -if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then - if [[ " ${BUILD_AND_PUSH_BRANCHES[*]} " == *"$TRAVIS_BRANCH"* ]]; then - echo "Pushing builded image to registry with tag -> ${DOCKER_TAG}" - - docker login -u $DOCKER_USER -p $DOCKER_AUTH - docker push $DOCKER_IMAGE - else - echo "Pushed branch is not targeted environment branch (development, stage, master). Image is not pushed to registry" - fi -else - echo "Image is not pushed to registry for pull requests" -fi diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 672fb8d..0000000 --- a/webpack.config.js +++ /dev/null @@ -1,98 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -const path = require("path"); -const webpack = require("webpack"); -const autoprefixer = require("autoprefixer"); -const modulesValues = require("postcss-modules-values"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); - -function getDevtool(env) { - return env === "development" ? "eval" : "cheap-module-source-map"; -} - -function getEntry(env) { - if (env === "development") { - return [ - "webpack-dev-server/client?http://0.0.0.0:3000", - "webpack/hot/only-dev-server", - "react-hot-loader/patch", - "babel-polyfill", - "whatwg-fetch", - "./src/index", - ]; - } - return ["babel-polyfill", "whatwg-fetch", "./src/index"]; -} - -function getPlugins(env) { - if (env === "development") { - return [ - new webpack.DefinePlugin({ - "process.env": { NODE_ENV: JSON.stringify("development") }, - }), - new webpack.HotModuleReplacementPlugin(), - new HtmlWebpackPlugin({ template: "index.ejs" }), - ]; - } - return [ - new webpack.optimize.OccurenceOrderPlugin(), - new webpack.DefinePlugin({ - "process.env": { NODE_ENV: JSON.stringify("production") }, - }), - new webpack.optimize.UglifyJsPlugin({ compressor: { warnings: false } }), - new HtmlWebpackPlugin({ template: "index.ejs" }), - ]; -} - -module.exports = { - bail: process.env.NODE_ENV !== "development", - devtool: getDevtool(process.env.NODE_ENV), - entry: getEntry(process.env.NODE_ENV), - plugins: getPlugins(process.env.NODE_ENV), - resolve: { - modulesDirectories: ["node_modules", "src"], - }, - output: { - path: path.join(__dirname, "dist"), - publicPath: "/kuljettaja/", - filename: "bundle.js", - }, - module: { - loaders: [ - { - test: /\.js$/, - loaders: ["babel"], - exclude: /node_modules/, - }, - { - test: /\.css$/, - loaders: [ - "style", - "css?modules&importLoaders=1&localIdentName=[name]_[local]_[hash:base64:5]", - "postcss", - ], - exclude: [ - path.join(__dirname, "node_modules", "leaflet"), - path.join(__dirname, "node_modules", "leaflet.fullscreen"), - ], - }, - { - test: /\.css$/, - loaders: ["style", "css", "postcss"], - include: [ - path.join(__dirname, "node_modules", "leaflet"), - path.join(__dirname, "node_modules", "leaflet.fullscreen"), - ], - }, - { - test: /\.svg$/, - loader: "url-loader?mimetype=image/svg+xml", - }, - { - test: /\.png$/, - loader: "url-loader", - query: { mimetype: "image/png" }, - }, - ], - }, - postcss: [modulesValues, autoprefixer], -};