Commit 6fbe158b authored by Oleksii Savran's avatar Oleksii Savran Committed by Matija Obreza
Browse files

Map of accessions

parent 5d95215b
......@@ -24,7 +24,7 @@ module.exports = {
commonjs: 'lodash',
commonjs2: 'lodash',
amd: 'lodash',
root: '_'
root: '_',
},
},
......@@ -39,53 +39,53 @@ module.exports = {
module: {
rules: [
{
test: /\.tsx?$/,
enforce: 'pre',
loader: 'eslint-loader',
exclude: /node_modules/,
{
test: /\.tsx?$/,
enforce: 'pre',
loader: 'eslint-loader',
exclude: /node_modules/,
},
{
test: /\.tsx?$/,
use: {
loader: 'awesome-typescript-loader',
},
{
test: /\.tsx?$/,
use: {
loader: 'awesome-typescript-loader',
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false,
},
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: false
},
},
'resolve-url-loader', {
loader: 'sass-loader',
options: {
sourceMap: true,
sassOptions: {
includePaths: ['node_modules']
}
}
}
]
},
]
},
'resolve-url-loader', {
loader: 'sass-loader',
options: {
sourceMap: true,
sassOptions: {
includePaths: ['node_modules'],
},
},
},
],
},
],
},
optimization: {
noEmitOnErrors: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
deps: {
test: /[\\/]node_modules[\\/]/,
name: 'deps',
chunks: 'all'
},
},
// cacheGroups: {
// deps: {
// test: /[\\/]node_modules[\\/]/,
// name: 'deps',
// chunks: 'all'
// },
// },
},
}
},
};
......@@ -9,6 +9,9 @@
<meta name="author" content="Genesys Team, helpdesk@genesys-pgr.org" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css"
integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
</head>
<body>
......@@ -22,6 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="#/overview">Overview</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#/map">Map</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#/cart">Cart</a>
</li>
......
......@@ -14,6 +14,7 @@ export class Config {
public shoppingCart?: BaseFeatureConfig;
public recaptchaSiteKey?: string;
public title?: string;
public map?: MapConfig;
public constructor(config: Config) {
this.apiUrl = config.apiUrl || defaultConfig.apiUrl;
......@@ -27,6 +28,7 @@ export class Config {
// Merge feature config
this.accession = { ...defaultConfig.accession, ...config.accession };
this.shoppingCart = { ...defaultConfig.shoppingCart, ...config.shoppingCart };
this.map = { ...defaultConfig.map, ...config.map };
// console.log('Source and merged configuration', config, this);
}
}
......@@ -41,6 +43,13 @@ class AccessionConfig extends BaseFeatureConfig {
public datasets?: boolean;
}
export class MapConfig extends BaseFeatureConfig {
public baseMap: {
url: string,
attribution: string,
};
}
export const defaultConfig: Partial<Config> = {
apiUrl: 'https://api.sandbox.genesys-pgr.org',
filter: {},
......@@ -55,4 +64,11 @@ export const defaultConfig: Partial<Config> = {
shoppingCart: {
enabled: false,
},
map: {
enabled: true,
baseMap: {
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}/',
attribution: 'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ',
},
},
};
{
"loading": "Loading...",
"label": {
"andMore": "And {{otherMore, number}} more"
},
"pag": {
"first": "First",
"prev": "Prev",
......
import React from 'react';
import { LeafletMouseEvent } from 'leaflet';
import { fixLongitude } from '@genesys/client/utilities';
import { AccessionService } from '@genesys/client/service';
import AccessionFilter from '@genesys/client/model/accession/AccessionFilter';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const Rectangle = React.lazy(() => {
return import( /* webpackChunkName: "leaflet" */ 'react-leaflet').then((module) => {
return { default: module.Rectangle };
});
});
const Popup = React.lazy(() => {
return import( /* webpackChunkName: "leaflet" */ 'react-leaflet').then((module) => {
return { default: module.Popup };
});
});
let useMapEvents;
import( /* webpackChunkName: "leaflet" */ 'react-leaflet').then((module) => {
useMapEvents = module.useMapEvents;
});
interface ILocationMarkerProps {
filter: AccessionFilter;
}
export default function LocationMarker(props: ILocationMarkerProps) {
const POPUP_CONTENT_LIMIT = 11;
const { t } = useTranslation();
const popupRef = React.useRef();
const [bounds, setBounds] = React.useState(null);
const [geoData, setGeoData] = React.useState(null);
const [otherCount, setOtherCount] = React.useState(null);
const map = useMapEvents({
click(e: LeafletMouseEvent) {
const { filter } = props;
const currentZoom = map.getZoom();
setBounds(null);
// console.log('click', currentZoom, e, map);
if (currentZoom < 4) {
return;
}
map.flyTo(e.latlng, currentZoom);
const correctLng = fixLongitude(e.latlng.lng);
const width = Math.pow(2, currentZoom - 2);
const searchBounds = [
[e.latlng.lat - (3 / width), correctLng - (3 / width)],
[e.latlng.lat + (3 / width), correctLng + (3 / width)],
];
const rectPos = [
[e.latlng.lat - (3 / width), e.latlng.lng - (3 / width)],
[e.latlng.lat + (3 / width), e.latlng.lng + (3 / width)],
];
const filterWithGeo = {
...filter,
geo: {
longitude: {
ge: searchBounds[0][1],
le: searchBounds[1][1],
},
latitude: {
ge: searchBounds[0][0],
le: searchBounds[1][0],
},
climate: filter && filter.geo && filter.geo.climate,
},
};
AccessionService
.geoJson(filterWithGeo, POPUP_CONTENT_LIMIT)
.then((res) => {
if (res.geoJson && res.geoJson.length) {
setBounds(rectPos);
setGeoData(res.geoJson);
setOtherCount(res.otherCount);
map.openPopup(popupRef.current, e.latlng);
}
})
.catch((e) => console.log('Error happened while requesting geo data: ', e));
},
});
return (
<Rectangle bounds={ bounds || [[0, 1], [0, 1]] } opacity={ bounds ? 1 : undefined }>
<Popup ref={ popupRef }>
{ geoData && geoData.map((feature) => (
<div key={ feature.properties.uuid }>
<Link to={ `/a/${feature.properties.uuid}` }>
{ `${feature.properties.accessionNumber} ${feature.properties.instCode}` }
</Link>
</div>
)) }
{ otherCount > 0 && <div>{ t('label.andMore', { otherMore: otherCount }) }</div> }
</Popup>
</Rectangle>
);
};
import React, { Suspense } from 'react';
import { withTranslation, WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import AccessionFilter from '@genesys/client/model/accession/AccessionFilter';
import { AccessionService } from '@genesys/client/service';
import AccessionMapInfo from '@genesys/client/model/accession/AccessionMapInfo';
import { WithConfig } from 'config/config';
const MapContainer = React.lazy(() => {
return import( /* webpackChunkName: "leaflet" */ 'react-leaflet').then((module) => {
return { default: module.MapContainer }; // leaflet doesn't have default export but React.lazy requires it
});
});
const TileLayer = React.lazy(() => {
return import( /* webpackChunkName: "leaflet" */ 'react-leaflet').then((module) => {
return { default: module.TileLayer };
});
});
const LocationMarker = React.lazy(() => import( /* webpackChunkName: "leaflet" */ './LocationMarker'));
interface IMapComponentProps {
filter?: AccessionFilter;
height?: number;
pageMode?: boolean;
}
interface IMapComponentState {
mapInfo: AccessionMapInfo;
}
class MapComponent extends React.Component<IMapComponentProps & WithTranslation & WithConfig, IMapComponentState> {
public static defaultLayer: {
name: 'Current selection',
default: true,
enabled: true,
opacity: 1,
color: '578218',
};
public state = {
mapInfo: null,
};
public componentDidMount() {
if (typeof window !== 'undefined') {
// fix leaflet styles, won't work without it
const style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.leaflet-container { height: inherit; width: inherit; }';
document.getElementsByTagName('head')[0].appendChild(style);
// load data
const { filter } = this.props;
AccessionService
.mapInfo(filter)
.then((mapInfo: AccessionMapInfo) => {
this.setState({ mapInfo });
console.log('-----', mapInfo);
})
.catch((e) => {
console.log('Error happened while loading map info', e);
})
}
}
public render() {
const { t, height = 500, filter: propFilter = null, pageMode = true, appConfig } = this.props;
const filter = propFilter || appConfig.filter;
const { url, attribution } = appConfig.map.baseMap;
const { mapInfo } = this.state;
const layerUrl = `{s}/acn/tile/{z}/{x}/{y}?f=${mapInfo && mapInfo.filterCode ? mapInfo.filterCode : ''}`;
if (mapInfo && mapInfo.bounds) {
if (!mapInfo.bounds[0] || !mapInfo.bounds[1] || !mapInfo.bounds[0][0] || !mapInfo.bounds[0][1] || !mapInfo.bounds[1][0] || !mapInfo.bounds[1][1]) {
mapInfo.bounds = [[-170, 80], [170, -80]];
}
}
return (
<div style={ {
height: pageMode ? 'calc(100vh - 115px)' : `${height}px`, // map container must have fixed height
width: '100%',
minHeight: `${height}px`,
} }>
<Suspense fallback={ <div>{ t('loading') }</div> }>
{ mapInfo &&
<MapContainer
bounds={ mapInfo.bounds }
zoom={ 4 }
// scrollWheelZoom={ scrollWheelZoom }
minZoom={ 2 }
maxZoom={ 14 }
>
<TileLayer
attribution={ attribution }
url={ url }
/>
<TileLayer
{ ...MapComponent.defaultLayer }
zIndex={ 2 }
updateInterval={ 1000 }
updateWhenZooming={ false }
attribution="&amp;copy Accession localities from <a href=&quot;/&quot;>Genesys PGR</a>"
url={ layerUrl }
subdomains={ mapInfo.tileServers }
/>
{ pageMode && (
<Suspense fallback={ <div>{ t('loading') }</div> }>
<LocationMarker filter={ filter }/>
</Suspense>
) }
</MapContainer>
}
</Suspense>
</div>
);
}
}
const mapStateToProps = (state) => ({
appConfig: state.appConfig.config,
});
export default connect(mapStateToProps)(withTranslation()(MapComponent));
import * as React from 'react';
// import { WithTranslation, withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import MapComponent from './MapComponent';
import AccessionFilter from '@genesys/client/model/accession/AccessionFilter';
interface IMapPageProps {
filter: AccessionFilter;
}
class MapPage extends React.Component<IMapPageProps, any> {
// private mapRef = null;
// private rectRef = null;
public state = {};
public constructor(props, context) {
super(props, context);
}
public render() {
const { filter } = this.props;
return (
<MapComponent filter={ filter }/>
);
}
}
const mapStateToProps = (state) => ({
filter: state.appConfig.config.filter,
});
export default connect(mapStateToProps)(MapPage);
......@@ -13,6 +13,7 @@ import { WithConfig } from 'config/config';
import CartPage from 'request/CartPage';
import OverviewPage from 'accession/OverviewPage';
import RequestPage from 'request/RequestPage';
import MapPage from 'map/MapPage';
const hashHistory = createHashHistory({});
......@@ -27,7 +28,7 @@ class App extends React.Component<IAppProps & WithTranslation & WithConfig, any>
}
public render() {
const { appConfig: { shoppingCart } } = this.props;
const { appConfig: { shoppingCart, map } } = this.props;
return (
<>
......@@ -40,6 +41,7 @@ class App extends React.Component<IAppProps & WithTranslation & WithConfig, any>
{ shoppingCart.enabled && <Route path="/cart/" exact component={ CartPage }/> }
<Route path="/overview" exact component={ OverviewPage }/>
{ shoppingCart.enabled && <Route path="/request" exact component={ RequestPage }/> }
{ map.enabled && <Route path="/map" exact component={ MapPage }/> }
<Route component={ NotFound }/>
</Switch>
</div>
......
......@@ -38,11 +38,21 @@
dependencies:
regenerator-runtime "^0.13.4"
"@react-leaflet/core@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-1.0.2.tgz#39c6a73f61c666d5dcf673741cea2672fa4bbae1"
integrity sha512-QbleYZTMcgujAEyWGki8Lx6cXQqWkNtQlqf5c7NImlIp8bKW66bFpez/6EVatW7+p9WKBOEOVci/9W7WW70EZg==
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
"@types/geojson@*":
version "7946.0.7"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad"
integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ==
"@types/glob@^7.1.1":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
......@@ -66,6 +76,13 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/leaflet@^1.0.0":
version "1.5.19"
resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.5.19.tgz#f290e21fe7a4744d68bb4b0709e99ce490120e6e"
integrity sha512-ZAKqfvdU/+KFoCpf8aUba09F8mfSc8R2esq++Cha3E2DgwS5K/I/4eJ+0JylrVHZivgY7PSAeXFv/izP+81/MQ==
dependencies:
"@types/geojson" "*"
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
......@@ -3829,6 +3846,11 @@ last-call-webpack-plugin@^3.0.0:
lodash "^4.17.5"
webpack-sources "^1.1.0"
leaflet@^1.0.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19"
integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
......@@ -5304,6 +5326,13 @@ react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-leaflet@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-3.0.2.tgz#4133a0550efe7e24be272b9e24e72fa98b51b751"
integrity sha512-7JxMV9HOo94jFwn1z+gcHVcYWeMxD994YsLxX3yyWebGOsLxpYfiQgNI7RPZWcAtcBK8XYSRB6WUj8NNb9hgbw==
dependencies:
"@react-leaflet/core" "^1.0.2"
react-redux@^7.0.0:
version "7.2.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment