diff --git a/package-lock.json b/package-lock.json index 514bc7c76d..7b7b522b3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@fortawesome/free-solid-svg-icons": "^6.0.0", "@fortawesome/react-fontawesome": "^0.1.17", "@terrestris/base-util": "^1.0.1", - "@terrestris/ol-util": "^5.1.0", + "@terrestris/ol-util": "^6.0.0", "@types/geojson": "^7946.0.8", "@types/lodash": "^4.14.179", "@types/moment": "^2.13.0", @@ -3642,9 +3642,9 @@ } }, "node_modules/@terrestris/ol-util": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@terrestris/ol-util/-/ol-util-5.1.0.tgz", - "integrity": "sha512-IDmqh0H47uSmtZWAWYzILW1Vi4CUvUKgIhL5Vj3OgQ+mYA4k9vL3QvisRWxun3cH6Ra1fgTDy+39i7AT953QdA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@terrestris/ol-util/-/ol-util-6.0.0.tgz", + "integrity": "sha512-99km/hVLd0JS6y7zCNnD1Bf9IW2lpiUWfKcawvhkyVkEujsSP4zlgl6QwCiYgWQYUsoCAyNZHeiFVoXklEwW+A==", "dependencies": { "@terrestris/base-util": "^1.0.1", "@turf/turf": "^6.5.0", @@ -3662,6 +3662,18 @@ "ol": "^6.5" } }, + "node_modules/@terrestris/ol-util/node_modules/typescript": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", + "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@testing-library/dom": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.3.tgz", diff --git a/package.json b/package.json index 952462cdab..0010101b2b 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "@fortawesome/free-solid-svg-icons": "^6.0.0", "@fortawesome/react-fontawesome": "^0.1.17", "@terrestris/base-util": "^1.0.1", - "@terrestris/ol-util": "^5.1.0", + "@terrestris/ol-util": "^6.0.0", "@types/geojson": "^7946.0.8", "@types/lodash": "^4.14.179", "@types/moment": "^2.13.0", diff --git a/src/Button/CopyButton/CopyButton.tsx b/src/Button/CopyButton/CopyButton.tsx index 9660b95584..a99b5c6287 100644 --- a/src/Button/CopyButton/CopyButton.tsx +++ b/src/Button/CopyButton/CopyButton.tsx @@ -64,7 +64,7 @@ const CopyButton: React.FC = ({ const feat = event.selected[0]; - if (!feat || !layers) { + if (!feat || !layers || !map) { return; } diff --git a/src/Button/DigitizeButton/DigitizeButton.tsx b/src/Button/DigitizeButton/DigitizeButton.tsx index 9483d665ba..5ef4fef0b6 100644 --- a/src/Button/DigitizeButton/DigitizeButton.tsx +++ b/src/Button/DigitizeButton/DigitizeButton.tsx @@ -13,12 +13,13 @@ import OlStyleFill from 'ol/style/Fill'; import OlStyleCircle from 'ol/style/Circle'; import OlStyleText from 'ol/style/Text'; import OlInteractionDraw, { createBox, DrawEvent as OlDrawEvent} from 'ol/interaction/Draw'; -import OlInteractionSelect from 'ol/interaction/Select'; -import OlInteractionModify from 'ol/interaction/Modify'; -import OlInteractionTranslate from 'ol/interaction/Translate'; +import OlInteractionSelect, { SelectEvent as OlSelectEvent } from 'ol/interaction/Select'; +import OlInteractionModify, { ModifyEvent as OlModifyEvent } from 'ol/interaction/Modify'; +import OlInteractionTranslate, { TranslateEvent as OlTranslateEvent } from 'ol/interaction/Translate'; import OlFeature from 'ol/Feature'; import * as OlEventConditions from 'ol/events/condition'; import OlGeometry from 'ol/geom/Geometry'; +import OlMapBrowserEvent from 'ol/MapBrowserEvent'; import _isFunction from 'lodash/isFunction'; import _isArray from 'lodash/isArray'; @@ -30,6 +31,7 @@ import AnimateUtil from '@terrestris/ol-util/dist/AnimateUtil/AnimateUtil'; import Logger from '@terrestris/base-util/dist/Logger'; import { CSS_PREFIX } from '../../constants'; +import { ChangeEvent } from 'react'; interface DefaultProps { /** @@ -524,7 +526,7 @@ class DigitizeButton extends React.Component { + digitizeStyleFunction = (feature: OlFeature) => { const { drawStyle, } = this.props; - if (!feature.getGeometry()) { - return null; + const geometry = feature.getGeometry(); + if (!geometry) { + return undefined; } if (drawStyle) { return _isFunction(drawStyle) ? drawStyle(feature) : drawStyle; } - switch (feature.getGeometry().getType()) { + switch (geometry.getType()) { case DigitizeButton.POINT_DRAW_TYPE: { if (!feature.get('isLabel')) { return new OlStyleStyle({ @@ -617,7 +620,7 @@ class DigitizeButton extends React.Component { const { @@ -708,7 +708,7 @@ class DigitizeButton extends React.Component { - const onDrawEnd = this.getOnDrawEnd(); - if (onDrawEnd) { - onDrawEnd(evt); - } + drawInteraction.on('drawend', (evt: OlDrawEvent) => { + this.getOnDrawEnd()?.(evt); }); - drawInteraction.on('drawstart', (evt) => { - const onDrawStart = this.getOnDrawStart(); - if (onDrawStart) { - onDrawStart(evt); - } + drawInteraction.on('drawstart', (evt: OlDrawEvent) => { + this.getOnDrawStart()?.(evt); }); drawInteraction.setActive(false); @@ -774,7 +768,7 @@ class DigitizeButton extends React.Component { + onFeatureRemove = (evt: OlSelectEvent) => { const { onFeatureRemove, onFeatureSelect @@ -919,7 +913,7 @@ class DigitizeButton extends React.Component { + onFeatureCopy = (evt: OlSelectEvent) => { const { map, onFeatureCopy, @@ -928,7 +922,7 @@ class DigitizeButton extends React.Component { + onModifyStart = (evt: OlModifyEvent) => { const { onModifyStart } = this.props; @@ -971,7 +965,7 @@ class DigitizeButton extends React.Component; if (feature.get('isLabel')) { this._digitizeTextFeature = feature; @@ -997,7 +991,7 @@ class DigitizeButton extends React.Component { + onModifyEnd = (evt: OlModifyEvent) => { const { onModifyEnd } = this.props; @@ -1012,7 +1006,7 @@ class DigitizeButton extends React.Component { + onTranslateStart = (evt: OlTranslateEvent) => { const { onTranslateStart } = this.props; @@ -1027,7 +1021,7 @@ class DigitizeButton extends React.Component { + onTranslateEnd = (evt: OlTranslateEvent) => { const { onTranslateEnd } = this.props; @@ -1042,7 +1036,7 @@ class DigitizeButton extends React.Component { + onTranslating = (evt: OlTranslateEvent) => { const { onTranslating } = this.props; @@ -1146,7 +1140,7 @@ class DigitizeButton extends React.Component { + onLabelChange = (evt: ChangeEvent) => { this.setState({ textLabel: evt.target.value }); @@ -1158,7 +1152,7 @@ class DigitizeButton extends React.Component { + onPointerMove = (evt: OlMapBrowserEvent) => { if (evt.dragging) { return; } diff --git a/src/Button/DrawButton/DrawButton.tsx b/src/Button/DrawButton/DrawButton.tsx index 8c1699574b..364c0c9310 100644 --- a/src/Button/DrawButton/DrawButton.tsx +++ b/src/Button/DrawButton/DrawButton.tsx @@ -15,6 +15,7 @@ import { CSS_PREFIX } from '../../constants'; import { useMap } from '../../Hook/useMap'; import { DigitizeUtil } from '../../Util/DigitizeUtil'; import { FeatureLabelModal } from '../../FeatureLabelModal/FeatureLabelModal'; +import { EventsKey } from 'ol/events'; type DrawType = 'Point' | 'LineString' | 'Polygon' | 'Circle' | 'Rectangle' | 'Text'; @@ -165,7 +166,7 @@ const DrawButton: React.FC = ({ setDrawInteraction(newInteraction); - let key; + let key: EventsKey; if (drawType === 'Text') { key = newInteraction.on('drawend', evt => { diff --git a/src/Button/GeoLocationButton/GeoLocationButton.tsx b/src/Button/GeoLocationButton/GeoLocationButton.tsx index 119f0fc6ea..7dcbb53d00 100644 --- a/src/Button/GeoLocationButton/GeoLocationButton.tsx +++ b/src/Button/GeoLocationButton/GeoLocationButton.tsx @@ -273,30 +273,28 @@ class GeoLocationButton extends React.Component { // recenters the view by putting the given coordinates at 3/4 from the top or // the screen - getCenterWithHeading = (position, rotation, resolution) => { + getCenterWithHeading = (position: [number, number], rotation: number, resolution: number) => { const size = this.props.map.getSize() ?? [0, 0]; const height = size[1]; return [ - position[0] - Math.sin(rotation) * height * resolution * 1 / 4, - position[1] + Math.cos(rotation) * height * resolution * 1 / 4 + position[0] - Math.sin(rotation) * height * resolution / 4, + position[1] + Math.cos(rotation) * height * resolution / 4 ]; }; updateView = () => { const view = this.props.map.getView(); const deltaMean = 500; // the geolocation sampling period mean in ms - let previousM = 0; // use sampling period to get a smooth transition let m = Date.now() - deltaMean * 1.5; - m = Math.max(m, previousM); + m = Math.max(m, 0); - previousM = m; // interpolate position along positions LineString const c = this._positions.getCoordinateAtM(m, true); if (c) { if (this.props.follow) { - view.setCenter(this.getCenterWithHeading(c, -c[2], view.getResolution())); + view.setCenter(this.getCenterWithHeading([c[0], c[1]], -c[2], view.getResolution() ?? 0)); view.setRotation(-c[2]); } if (this.props.showMarker) { diff --git a/src/Button/MeasureButton/MeasureButton.tsx b/src/Button/MeasureButton/MeasureButton.tsx index 6b1d235431..4b1a9e2b71 100644 --- a/src/Button/MeasureButton/MeasureButton.tsx +++ b/src/Button/MeasureButton/MeasureButton.tsx @@ -462,10 +462,8 @@ class MeasureButton extends React.Component { /** * Called if the current geometry of the draw interaction has changed. - * - * @param evt The generic change event. */ - onDrawInteractionGeometryChange(/* evt*/) { + onDrawInteractionGeometryChange() { this.updateMeasureTooltip(); } @@ -608,8 +606,8 @@ class MeasureButton extends React.Component { } const value = measureType === 'line' ? - MeasureUtil.formatLength(geom, map, decimalPlacesInTooltips, geodesic) : - MeasureUtil.formatArea(geom, map, decimalPlacesInTooltips, geodesic); + MeasureUtil.formatLength(geom as OlGeomLineString, map, decimalPlacesInTooltips, geodesic) : + MeasureUtil.formatArea(geom as OlGeomPolygon, map, decimalPlacesInTooltips, geodesic); if (parseInt(value, 10) > 0) { const div = document.createElement('div'); @@ -843,13 +841,15 @@ class MeasureButton extends React.Component { if (measureType === 'line') { output = MeasureUtil.formatLength(geom, map, decimalPlacesInTooltips, geodesic); } else if (measureType === 'angle') { - output = MeasureUtil.formatAngle(geom, map, decimalPlacesInTooltips); + output = MeasureUtil.formatAngle(geom, 0); } } else { return; } - this._measureTooltipElement.innerHTML = output; + if (output) { + this._measureTooltipElement.innerHTML = output; + } this._measureTooltip?.setPosition(measureTooltipCoord); } } diff --git a/src/Button/ToggleGroup/ToggleGroup.tsx b/src/Button/ToggleGroup/ToggleGroup.tsx index b36b50c19b..eb5290b614 100644 --- a/src/Button/ToggleGroup/ToggleGroup.tsx +++ b/src/Button/ToggleGroup/ToggleGroup.tsx @@ -6,6 +6,7 @@ import _isFunction from 'lodash/isFunction'; import { CSS_PREFIX } from '../../constants'; import './ToggleGroup.less'; +import { ToggleButtonProps } from '../ToggleButton/ToggleButton'; export interface ToggleGroupProps { /** @@ -124,7 +125,7 @@ class ToggleGroup extends React.Component { * * @param childProps The properties of the children. */ - onChange = (childProps) => { + onChange = (childProps: ToggleButtonProps) => { if (_isFunction(this.props.onChange)) { this.props.onChange(childProps); } diff --git a/src/Container/AddWmsPanel/AddWmsLayerEntry/AddWmsLayerEntry.tsx b/src/Container/AddWmsPanel/AddWmsLayerEntry/AddWmsLayerEntry.tsx index 1a33a42cb8..b02dbe3a0c 100644 --- a/src/Container/AddWmsPanel/AddWmsLayerEntry/AddWmsLayerEntry.tsx +++ b/src/Container/AddWmsPanel/AddWmsLayerEntry/AddWmsLayerEntry.tsx @@ -47,7 +47,7 @@ export class AddWmsLayerEntry extends React.Component { + layerTextTemplateFn: (wmsLayer: WmsLayer) => { const title = wmsLayer.get('title'); const abstract = wmsLayer.get('abstract'); return abstract ? diff --git a/src/Field/CoordinateReferenceSystemCombo/CoordinateReferenceSystemCombo.tsx b/src/Field/CoordinateReferenceSystemCombo/CoordinateReferenceSystemCombo.tsx index 88f317a9b7..5ab62f6256 100644 --- a/src/Field/CoordinateReferenceSystemCombo/CoordinateReferenceSystemCombo.tsx +++ b/src/Field/CoordinateReferenceSystemCombo/CoordinateReferenceSystemCombo.tsx @@ -118,7 +118,7 @@ class CoordinateReferenceSystemCombo extends React.Component { const results = json.results; if (results && results.length > 0) { - return results.map(obj => ({code: obj.code, value: obj.name, proj4def: obj.proj4, bbox: obj.bbox})); + return results.map((obj: any) => ({code: obj.code, value: obj.name, proj4def: obj.proj4, bbox: obj.bbox})); } else { return []; } diff --git a/src/Field/NominatimSearch/NominatimSearch.tsx b/src/Field/NominatimSearch/NominatimSearch.tsx index 80f887a29c..19f1e08f47 100644 --- a/src/Field/NominatimSearch/NominatimSearch.tsx +++ b/src/Field/NominatimSearch/NominatimSearch.tsx @@ -173,6 +173,7 @@ export class NominatimSearch extends React.Component { if (selected && selected.boundingbox) { @@ -222,7 +223,7 @@ export class NominatimSearch extends React.Component) { if (this.props.searchTerm !== prevProps.searchTerm) { this.onUpdateInput(this.props.searchTerm); } diff --git a/src/Field/ScaleCombo/ScaleCombo.tsx b/src/Field/ScaleCombo/ScaleCombo.tsx index bbf39a96e4..87b9240513 100644 --- a/src/Field/ScaleCombo/ScaleCombo.tsx +++ b/src/Field/ScaleCombo/ScaleCombo.tsx @@ -4,6 +4,7 @@ const Option = Select.Option; import OlMap from 'ol/Map'; import OlView from 'ol/View'; +import OlMapEvent from 'ol/MapEvent'; import _isInteger from 'lodash/isInteger'; import _isEmpty from 'lodash/isEmpty'; @@ -19,6 +20,7 @@ import MapUtil from '@terrestris/ol-util/dist/MapUtil/MapUtil'; import { CSS_PREFIX } from '../../constants'; import './ScaleCombo.less'; +import _isNumber from 'lodash/isNumber'; interface ScaleComboProps { /** @@ -111,7 +113,7 @@ class ScaleCombo extends React.Component { const defaultOnZoomLevelSelect = (selectedScale: string) => { const mapView = props.map.getView(); const calculatedResolution = MapUtil.getResolutionForScale( - selectedScale, mapView.getProjection().getUnits() + parseInt(selectedScale, 10), mapView.getProjection().getUnits() ); mapView.setResolution(calculatedResolution); }; @@ -180,11 +182,11 @@ class ScaleCombo extends React.Component { * @param evt The 'moveend' event * @private */ - zoomListener = (evt) => { - const zoom = evt.target.getView().getZoom(); - let roundZoom = Math.round(zoom); - if (!roundZoom) { - roundZoom = 0; + zoomListener = (evt: OlMapEvent) => { + const zoom = (evt.target as OlMap).getView().getZoom(); + let roundZoom = 0; + if (_isNumber(zoom)) { + roundZoom = Math.round(zoom); } this.setState({ @@ -194,14 +196,14 @@ class ScaleCombo extends React.Component { /** * @function pushScaleOption: Helper function to create a {@link Option} scale component - * based on a resolution and the {@link Ol.View} + * based on a resolution and the {@link OlView} * * @param scales The scales array to push the scale to. * @param resolution map cresolution to generate the option for * @param view The map view * */ - pushScale = (scales: string[], resolution: number, view: OlView) => { + pushScale = (scales: number[], resolution: number, view: OlView) => { const scale = MapUtil.getScaleForResolution(resolution, view.getProjection().getUnits()); const roundScale = MapUtil.roundScale(scale); if (scales.includes(roundScale) ) { @@ -212,7 +214,7 @@ class ScaleCombo extends React.Component { /** * Generates the scales to add as {@link Option} to the SelectField based on - * the given instance of {@link Ol.Map}. + * the given instance of {@link OlMap}. * * @return The array of scales. */ @@ -227,7 +229,7 @@ class ScaleCombo extends React.Component { return []; } - const scales = []; + const scales: number[] = []; const view = map.getView(); // use existing resolutions array if exists const resolutions = view.getResolutions(); diff --git a/src/Field/WfsSearchInput/WfsSearchInput.tsx b/src/Field/WfsSearchInput/WfsSearchInput.tsx index b57d054097..c9b46c5bd5 100644 --- a/src/Field/WfsSearchInput/WfsSearchInput.tsx +++ b/src/Field/WfsSearchInput/WfsSearchInput.tsx @@ -15,6 +15,8 @@ import _debounce from 'lodash/debounce'; import Logger from '@terrestris/base-util/dist/Logger'; import WfsFilterUtil from '@terrestris/ol-util/dist/WfsFilterUtil/WfsFilterUtil'; +import { Feature } from 'geojson'; + import { CSS_PREFIX } from '../../constants'; import './WfsSearchInput.less'; @@ -139,12 +141,12 @@ interface OwnProps { * Please note: if omitted only data fetch will be performed and no data * will be shown afterwards! */ - onFetchSuccess?: (data: any) => void; + onFetchSuccess?: (features: Feature[]) => void; /** * An onFetchError callback function which gets called if data fetch is * failed. */ - onFetchError?: (data: any) => void; + onFetchError?: (error: string) => void; /** * Optional callback function, that will be called if 'clear' button of * input field was clicked. @@ -159,7 +161,7 @@ interface OwnProps { interface WfsSearchState { searchTerm: string; - data: any[]; + data: Feature[]; fetching: boolean; } @@ -223,7 +225,7 @@ export class WfsSearchInput extends React.Component) { if (this.props.searchTerm !== prevProps.searchTerm) { const evt = { target: { @@ -314,18 +316,20 @@ export class WfsSearchInput extends React.Component { - fetch(`${baseUrl}`, { - method: 'POST', - credentials: additionalFetchOptions.credentials - ? additionalFetchOptions.credentials - : 'same-origin', - body: new XMLSerializer().serializeToString(request), - ...additionalFetchOptions - }) - .then(response => response.json()) - .then(this.onFetchSuccess.bind(this)) - .catch(this.onFetchError.bind(this)); + }, async () => { + try { + const response = await fetch(`${baseUrl}`, { + method: 'POST', + credentials: additionalFetchOptions.credentials + ? additionalFetchOptions.credentials + : 'same-origin', + body: new XMLSerializer().serializeToString(request), + ...additionalFetchOptions + }); + this.onFetchSuccess(await response.json()); + } catch (e) { + this.onFetchError(e.getMessage()) + } }); } else { this.onFetchError('Missing GetFeature request parameters'); @@ -343,15 +347,18 @@ export class WfsSearchInput extends React.Component feature.searchTerm = this.state.searchTerm); + const data: Feature[] = response.features ? response.features : []; + for (const feature of data) { + if (!feature.properties) { + feature.properties = {}; + } + feature.properties.searchTerm = this.state.searchTerm; + } this.setState({ data, fetching: false }, () => { - if (onFetchSuccess) { - onFetchSuccess(data); - } + onFetchSuccess?.(data); }); } @@ -371,9 +378,7 @@ export class WfsSearchInput extends React.Component { - if (onFetchError) { - onFetchError(error); - } + onFetchError?.(error); }); } diff --git a/src/Grid/AgFeatureGrid/AgFeatureGrid.tsx b/src/Grid/AgFeatureGrid/AgFeatureGrid.tsx index e213f1df00..267a46d458 100644 --- a/src/Grid/AgFeatureGrid/AgFeatureGrid.tsx +++ b/src/Grid/AgFeatureGrid/AgFeatureGrid.tsx @@ -1,5 +1,6 @@ /* eslint-disable testing-library/render-result-naming-convention */ import * as React from 'react'; +import { ReactNode } from 'react'; import OlStyle from 'ol/style/Style'; import OlStyleFill from 'ol/style/Fill'; @@ -34,7 +35,9 @@ import { RowClickedEvent, CellMouseOverEvent, CellMouseOutEvent, - SelectionChangedEvent + SelectionChangedEvent, + DetailGridInfo, + RowNode } from '@ag-grid-community/core'; import _isNil from 'lodash/isNil'; @@ -152,8 +155,8 @@ interface OwnProps { } interface AgFeatureGridState { - grid: any; - selectedRows: any[]; + grid: DetailGridInfo | null; + selectedRows: RowNode[]; } export type AgFeatureGridProps = OwnProps & AgGridReactProps; @@ -242,7 +245,7 @@ export class AgFeatureGrid extends React.Component { const key = this.props.keyFunction(feature); let rc: any; - rowRenderer.forEachRowComp((idx, rowComp) => { + rowRenderer.forEachRowComp((idx: number, rowComp: any) => { if (rowComp.getRowNode().data.key === key) { rc = rowComp; } @@ -469,7 +473,7 @@ export class AgFeatureGrid extends React.Component { const key = this.props.keyFunction(feature); let rc: any; - rowRenderer.forEachRowComp((idx, rowComp) => { + rowRenderer.forEachRowComp((idx: number, rowComp: any) => { if (rowComp.getRowNode().data.key === key) { rc = rowComp; } @@ -673,12 +677,12 @@ export class AgFeatureGrid extends React.Component { + getRowFromFeatureKey = (key: string): RowNode | undefined => { const { grid } = this.state; - let rowNode; + let rowNode: RowNode | undefined = undefined; if (!grid || !grid.api) { return; @@ -912,7 +916,7 @@ export class AgFeatureGrid extends React.Component { - const determinedRowClass = (rowClassName as ((record: any) => string))(node.data); + rowClassNameFn = (node: ReactNode) => { + const determinedRowClass = rowClassName((node as { data: string }).data); return `${this._rowClassName} ${determinedRowClass}`; }; } else { const finalRowClassName = rowClassName ? `${rowClassName} ${this._rowClassName}` : this._rowClassName; - rowClassNameFn = node => `${finalRowClassName} ${this._rowKeyClassNamePrefix}${_kebabCase(node.data.key)}`; + rowClassNameFn = (node: ReactNode) => { + const rowClassSuffix = _kebabCase((node as { data: { key: string } }).data.key); + return `${finalRowClassName} ${this._rowKeyClassNamePrefix}${rowClassSuffix}`; + }; } return ( diff --git a/src/HigherOrderComponent/TimeLayerAware/TimeLayerAware.tsx b/src/HigherOrderComponent/TimeLayerAware/TimeLayerAware.tsx index 2bac52a17b..48107ec55b 100644 --- a/src/HigherOrderComponent/TimeLayerAware/TimeLayerAware.tsx +++ b/src/HigherOrderComponent/TimeLayerAware/TimeLayerAware.tsx @@ -45,7 +45,7 @@ export function timeLayerAware

(WrappedComponent: React.ComponentType

, laye return class TimeLayerAware extends React.Component> { - timeChanged = newValues => { + timeChanged = (newValues: any) => { layers.forEach(config => { if (config.isWmsTime) { const parms = config.layer.getSource()?.getParams(); diff --git a/src/LayerSwitcher/LayerSwitcher.tsx b/src/LayerSwitcher/LayerSwitcher.tsx index 0b62846469..f82f08607f 100644 --- a/src/LayerSwitcher/LayerSwitcher.tsx +++ b/src/LayerSwitcher/LayerSwitcher.tsx @@ -118,10 +118,9 @@ export class LayerSwitcher extends React.Component | OlLayerGroup) => { - let layerClone: OlLayerTile | OlLayerGroup; + cloneLayer (layer: OlLayerTile | OlLayerGroup): OlLayerTile | OlLayerGroup { if (layer instanceof OlLayerGroup) { - layerClone = new OlLayerGroup({ + return new OlLayerGroup({ layers: layer.getLayers().getArray().map(this.cloneLayer), properties: { originalLayer: layer @@ -129,15 +128,14 @@ export class LayerSwitcher extends React.Component).getSource() || undefined, properties: { originalLayer: layer }, ...layer.getProperties() }); } - return layerClone; }; /** diff --git a/src/LayerTree/LayerTree.spec.tsx b/src/LayerTree/LayerTree.spec.tsx index 2dd4fee1ef..f4425cc84c 100644 --- a/src/LayerTree/LayerTree.spec.tsx +++ b/src/LayerTree/LayerTree.spec.tsx @@ -10,15 +10,17 @@ import TestUtil from '../Util/TestUtil'; import LayerTree from './LayerTree'; -import Logger from '@terrestris/base-util/dist/Logger'; +import OlMap from 'ol/Map'; +import OlLayerBase from 'ol/layer/Base'; +import { getUid } from 'ol'; describe('', () => { - let layerGroup; - let layerSubGroup; - let map; - let layer1; - let layer2; - let layer3; + let layerGroup: OlLayerGroup; + let layerSubGroup: OlLayerGroup; + let map: OlMap; + let layer1: OlLayerBase; + let layer2: OlLayerBase; + let layer3: OlLayerBase; beforeEach(() => { const layerSource1 = new OlSourceTileWMS(); @@ -80,9 +82,10 @@ describe('', () => { }); it('unmount removes listeners', () => { + // @ts-ignore OlObservable.unByKey = jest.fn(); const wrapper = TestUtil.mountComponent(LayerTree, {map}); - const olListenerKeys = wrapper.instance().olListenerKeys; + const olListenerKeys = (wrapper.instance() as LayerTree).olListenerKeys; wrapper.unmount(); expect(OlObservable.unByKey).toHaveBeenCalled(); expect(OlObservable.unByKey).toHaveBeenCalledWith(olListenerKeys); @@ -96,19 +99,23 @@ describe('', () => { const wrapper = TestUtil.mountComponent(LayerTree, props); const subLayer = new OlLayerTile({ - name: 'subLayer', + properties: { + name: 'subLayer' + }, source: new OlSourceTileWMS() }); const nestedLayerGroup = new OlLayerGroup({ - name: 'nestedLayerGroup', + properties: { + name: 'nestedLayerGroup' + }, layers: [subLayer] }); - expect(wrapper.instance().olListenerKeys).toHaveLength(10); + expect((wrapper.instance() as LayerTree).olListenerKeys).toHaveLength(10); wrapper.setProps({ layerGroup: nestedLayerGroup }); - expect(wrapper.instance().olListenerKeys).toHaveLength(4); + expect((wrapper.instance() as LayerTree).olListenerKeys).toHaveLength(4); }); describe(' creation', () => { @@ -155,11 +162,13 @@ describe('', () => { map }; const subLayer = new OlLayerTile({ - name: 'subLayer', + properties: { + name: 'subLayer' + }, source: new OlSourceTileWMS() }); const wrapper = TestUtil.mountComponent(LayerTree, props); - const rebuildSpy = jest.spyOn(wrapper.instance(), 'rebuildTreeNodes'); + const rebuildSpy = jest.spyOn((wrapper.instance() as LayerTree), 'rebuildTreeNodes'); map.getLayerGroup().setLayers(new OlCollection([subLayer])); expect(rebuildSpy).toHaveBeenCalled(); rebuildSpy.mockRestore(); @@ -172,7 +181,7 @@ describe('', () => { }; const wrapper = TestUtil.mountComponent(LayerTree, props); const treeNodes = wrapper.find('LayerTreeNode'); - treeNodes.forEach((node, index) => { + treeNodes.forEach((node: any, index: number) => { const reverseIndex = treeNodes.length - (index + 1); const layer = layerGroup.getLayers().item(reverseIndex); expect(node.props().title).toBe(layer.get('name')); @@ -180,7 +189,7 @@ describe('', () => { }); it('accepts a custom title renderer function', () => { - const nodeTitleRenderer = function(layer) { + const nodeTitleRenderer = function(layer: OlLayerBase) { return ( @@ -198,7 +207,7 @@ describe('', () => { const wrapper = TestUtil.mountComponent(LayerTree, props); const treeNodes = wrapper.find('LayerTreeNode'); - treeNodes.forEach((node, index) => { + treeNodes.forEach((node: any, index: number) => { const reverseIndex = treeNodes.length - (index + 1); const layer = layerGroup.getLayers().item(reverseIndex); expect(node.find('span.span-1').length).toBe(1); @@ -209,7 +218,7 @@ describe('', () => { }); it('can filter layers if a filterFunction is given', () => { - const filterFunction = function(layer) { + const filterFunction = function(layer: OlLayerBase) { return layer.get('name') !== 'layer1'; }; const props = { @@ -231,10 +240,10 @@ describe('', () => { const wrapper = TestUtil.mountComponent(LayerTree, props); const treeNodes = wrapper.find('LayerTreeNode'); - treeNodes.forEach((node, index) => { + treeNodes.forEach((node: any, index: number) => { const reverseIndex = treeNodes.length - (index + 1); const layer = layerGroup.getLayers().item(reverseIndex); - expect(node.props().eventKey).toBe(layer.ol_uid.toString()); + expect(node.props().eventKey).toBe(getUid(layer)); }); }); @@ -246,7 +255,7 @@ describe('', () => { const wrapper = TestUtil.mountComponent(LayerTree, props); const treeNodes = wrapper.find('LayerTreeNode'); - treeNodes.forEach((node, index) => { + treeNodes.forEach((node: any, index: number) => { const reverseIndex = treeNodes.length - (index + 1); const layer = layerGroup.getLayers().item(reverseIndex); expect(layer.getVisible()).toBe(node.props().checked); @@ -263,10 +272,10 @@ describe('', () => { layerGroup.setVisible(false); const wrapper = TestUtil.mountComponent(LayerTree, props); - const treeNode = wrapper.instance().treeNodeFromLayer(layer1); + const treeNode = (wrapper.instance() as LayerTree).treeNodeFromLayer(layer1); expect(treeNode.props.title).toBe(layer1.get('name')); - expect(treeNode.key).toBe(layer1.ol_uid.toString()); + expect(treeNode.key).toBe(getUid(layer1)); }); }); }); @@ -281,7 +290,7 @@ describe('', () => { const treeNodes = wrapper.find('LayerTreeNode'); - treeNodes.forEach((node, index) => { + treeNodes.forEach((node: any, index: number) => { const reverseIndex = treeNodes.length - (index + 1); const layer = layerGroup.getLayers().item(reverseIndex); const checkBox = node.find('.ant-tree-checkbox'); @@ -314,7 +323,7 @@ describe('', () => { }); it('triggers tree rebuild on nodeTitleRenderer changes', () => { - const exampleNodeTitleRenderer = function(layer) { + const exampleNodeTitleRenderer = function(layer: OlLayerBase) { return ( {layer.get('name')} @@ -327,7 +336,7 @@ describe('', () => { map }; const wrapper = TestUtil.mountComponent(LayerTree, props); - const rebuildSpy = jest.spyOn(wrapper.instance(), 'rebuildTreeNodes'); + const rebuildSpy = jest.spyOn((wrapper.instance() as LayerTree), 'rebuildTreeNodes'); wrapper.setProps({ nodeTitleRenderer: exampleNodeTitleRenderer @@ -351,7 +360,7 @@ describe('', () => { source: new OlSourceTileWMS() }); const wrapper = TestUtil.mountComponent(LayerTree, props); - const rebuildSpy = jest.spyOn(wrapper.instance(), 'rebuildTreeNodes'); + const rebuildSpy = jest.spyOn((wrapper.instance() as LayerTree), 'rebuildTreeNodes'); layerGroup.getLayers().push(layer); expect(rebuildSpy).toHaveBeenCalled(); @@ -371,8 +380,8 @@ describe('', () => { layers: [layer] }); const wrapper = TestUtil.mountComponent(LayerTree, props); - const rebuildSpy = jest.spyOn(wrapper.instance(), 'rebuildTreeNodes'); - const registerSpy = jest.spyOn(wrapper.instance(), 'registerAddRemoveListeners'); + const rebuildSpy = jest.spyOn((wrapper.instance() as LayerTree), 'rebuildTreeNodes'); + const registerSpy = jest.spyOn((wrapper.instance() as LayerTree), 'registerAddRemoveListeners'); layerGroup.getLayers().push(group); expect(rebuildSpy).toHaveBeenCalled(); @@ -388,8 +397,8 @@ describe('', () => { map }; const wrapper = TestUtil.mountComponent(LayerTree, props); - const rebuildSpy = jest.spyOn(wrapper.instance(), 'rebuildTreeNodes'); - const unregisterSpy = jest.spyOn(wrapper.instance(), 'unregisterEventsByLayer'); + const rebuildSpy = jest.spyOn((wrapper.instance() as LayerTree), 'rebuildTreeNodes'); + const unregisterSpy = jest.spyOn((wrapper.instance() as LayerTree), 'unregisterEventsByLayer'); layerGroup.getLayers().remove(layer1); expect(rebuildSpy).toHaveBeenCalled(); @@ -411,14 +420,16 @@ describe('', () => { source: new OlSourceTileWMS() }); const nestedLayerGroup = new OlLayerGroup({ - name: 'nestedLayerGroup', + properties: { + name: 'nestedLayerGroup' + }, layers: [subLayer1, subLayer2] }); layerGroup.getLayers().push(nestedLayerGroup); const wrapper = TestUtil.mountComponent(LayerTree, props); - const rebuildSpy = jest.spyOn(wrapper.instance(), 'rebuildTreeNodes'); - const unregisterSpy = jest.spyOn(wrapper.instance(), 'unregisterEventsByLayer'); + const rebuildSpy = jest.spyOn((wrapper.instance() as LayerTree), 'rebuildTreeNodes'); + const unregisterSpy = jest.spyOn((wrapper.instance() as LayerTree), 'unregisterEventsByLayer'); layerGroup.getLayers().remove(nestedLayerGroup); expect(rebuildSpy).toHaveBeenCalledTimes(1); @@ -442,18 +453,21 @@ describe('', () => { source: new OlSourceTileWMS() }); const nestedLayerGroup = new OlLayerGroup({ - name: 'nestedLayerGroup', + properties: { + name: 'nestedLayerGroup' + }, layers: [subLayer1, subLayer2] }); layerGroup.getLayers().push(nestedLayerGroup); const wrapper = TestUtil.mountComponent(LayerTree, props); - const oldOlListenerKey = wrapper.instance().olListenerKeys; + const oldOlListenerKey = (wrapper.instance() as LayerTree).olListenerKeys; + // @ts-ignore OlObservable.unByKey = jest.fn(); - wrapper.instance().unregisterEventsByLayer(subLayer1); + (wrapper.instance() as LayerTree).unregisterEventsByLayer(subLayer1); - const newOlListenerKey = wrapper.instance().olListenerKeys; + const newOlListenerKey = (wrapper.instance() as LayerTree).olListenerKeys; expect(OlObservable.unByKey).toHaveBeenCalled(); expect(newOlListenerKey.length).toBe(oldOlListenerKey.length - 1); @@ -471,18 +485,21 @@ describe('', () => { source: new OlSourceTileWMS() }); const nestedLayerGroup = new OlLayerGroup({ - name: 'nestedLayerGroup', + properties: { + name: 'nestedLayerGroup' + }, layers: [subLayer1, subLayer2] }); layerGroup.getLayers().push(nestedLayerGroup); const wrapper = TestUtil.mountComponent(LayerTree, props); - const oldOlListenerKey = wrapper.instance().olListenerKeys; + const oldOlListenerKey = (wrapper.instance() as LayerTree).olListenerKeys; + // @ts-ignore OlObservable.unByKey = jest.fn(); - wrapper.instance().unregisterEventsByLayer(nestedLayerGroup); + (wrapper.instance() as LayerTree).unregisterEventsByLayer(nestedLayerGroup); - const newOlListenerKey = wrapper.instance().olListenerKeys; + const newOlListenerKey = (wrapper.instance() as LayerTree).olListenerKeys; expect(OlObservable.unByKey).toHaveBeenCalledTimes(2); expect(newOlListenerKey.length).toBe(oldOlListenerKey.length - 2); @@ -501,9 +518,9 @@ describe('', () => { const wrapper = TestUtil.mountComponent(LayerTree, props); layer1.setVisible(true); - wrapper.instance().setLayerVisibility(layer1, false); + (wrapper.instance() as LayerTree).setLayerVisibility(layer1, false); expect(layer1.getVisible()).toBe(false); - wrapper.instance().setLayerVisibility(layer1, true); + (wrapper.instance() as LayerTree).setLayerVisibility(layer1, true); expect(layer1.getVisible()).toBe(true); }); @@ -516,12 +533,12 @@ describe('', () => { layer1.setVisible(true); layer2.setVisible(true); - wrapper.instance().setLayerVisibility(layerGroup, false); + (wrapper.instance() as LayerTree).setLayerVisibility(layerGroup, false); expect(layerGroup.getVisible()).toBe(false); expect(layer1.getVisible()).toBe(false); expect(layer2.getVisible()).toBe(false); - wrapper.instance().setLayerVisibility(layerGroup, true); + (wrapper.instance() as LayerTree).setLayerVisibility(layerGroup, true); expect(layerGroup.getVisible()).toBe(true); expect(layer1.getVisible()).toBe(true); expect(layer2.getVisible()).toBe(true); @@ -540,14 +557,14 @@ describe('', () => { layer3.setVisible(false); - wrapper.instance().setLayerVisibility(layer2, true); + (wrapper.instance() as LayerTree).setLayerVisibility(layer2, true); expect(layerGroup.getVisible()).toBe(true); expect(layer1.getVisible()).toBe(false); expect(layer2.getVisible()).toBe(true); expect(layer3.getVisible()).toBe(false); expect(layerSubGroup.getVisible()).toBe(false); - wrapper.instance().setLayerVisibility(layer1, true); + (wrapper.instance() as LayerTree).setLayerVisibility(layer1, true); expect(layerGroup.getVisible()).toBe(true); expect(layer1.getVisible()).toBe(true); expect(layer2.getVisible()).toBe(true); @@ -560,7 +577,7 @@ describe('', () => { layer2.setVisible(false); layer3.setVisible(false); - wrapper.instance().setLayerVisibility(layer3, true); + (wrapper.instance() as LayerTree).setLayerVisibility(layer3, true); expect(layer1.getVisible()).toBe(false); expect(layer2.getVisible()).toBe(false); expect(layer3.getVisible()).toBe(true); diff --git a/src/LayerTree/LayerTree.tsx b/src/LayerTree/LayerTree.tsx index 9947ba5fe6..8d865e8d55 100644 --- a/src/LayerTree/LayerTree.tsx +++ b/src/LayerTree/LayerTree.tsx @@ -18,6 +18,7 @@ import OlCollection from 'ol/Collection'; import OlMapEvent from 'ol/MapEvent'; import { unByKey } from 'ol/Observable'; import { getUid } from 'ol'; +import { EventsKey as OlEventsKey } from 'ol/events'; import _isFunction from 'lodash/isFunction'; import _isEqual from 'lodash/isEqual'; @@ -27,7 +28,6 @@ import MapUtil from '@terrestris/ol-util/dist/MapUtil/MapUtil'; import LayerTreeNode, { LayerTreeNodeProps } from './LayerTreeNode/LayerTreeNode'; import { CSS_PREFIX } from '../constants'; -import { EventsKey } from 'ol/events'; interface OwnProps { @@ -101,7 +101,7 @@ class LayerTree extends React.Component { * An array of ol.EventsKey as returned by on() or once(). * @private */ - olListenerKeys: EventsKey[] = []; + olListenerKeys: OlEventsKey[] = []; /** * Create the LayerTree. @@ -267,7 +267,7 @@ class LayerTree extends React.Component { onCollectionRemove = (evt: any) => { this.unregisterEventsByLayer(evt.element); if (evt.element instanceof OlLayerGroup) { - evt.element.getLayers().forEach((layer) => { + evt.element.getLayers().forEach((layer: OlLayerBase) => { this.unregisterEventsByLayer(layer); }); } @@ -283,7 +283,7 @@ class LayerTree extends React.Component { onChangeLayers = (evt: any) => { this.unregisterEventsByLayer(evt.oldValue); if (evt.oldValue instanceof OlCollection) { - evt.oldValue.forEach((layer) => this.unregisterEventsByLayer(layer)); + evt.oldValue.forEach((layer: OlLayerBase) => this.unregisterEventsByLayer(layer)); } if (evt.target instanceof OlLayerGroup) { this.registerAddRemoveListeners(evt.target); @@ -381,7 +381,7 @@ class LayerTree extends React.Component { return this.treeNodeFromLayer(childLayer); }).reverse(); } else { - if (!this.hasListener(layer, 'change:visible', this.onLayerChangeVisible)) { + if (!this.hasListener({ target: layer, type: 'change:visible', listener: this.onLayerChangeVisible })) { const eventKey = layer.on('change:visible', this.onLayerChangeVisible); this.olListenerKeys.push(eventKey); } @@ -401,24 +401,17 @@ class LayerTree extends React.Component { /** * Determines if the target has already registered the given listener for the * given eventtype. - * - * @param target The event target. - * @param type The events type (name). - * @param listener The function. - * @return True if the listener is already contained in this.olListenerKeys. */ - hasListener = (target, type, listener) => { + hasListener = (key: OlEventsKey) => { return this.olListenerKeys.some((listenerKey) => { - return listenerKey.target === target - && listenerKey.type === type - && listenerKey.listener === listener; + return listenerKey.target === key.target + && listenerKey.type === key.type + && listenerKey.listener === key.listener; }); }; /** * Reacts to the layer change:visible event and calls setCheckedState. - * - * @param evt The change:visible event */ onLayerChangeVisible = () => { const checkedKeys = this.getVisibleOlUids(); @@ -434,11 +427,16 @@ class LayerTree extends React.Component { * * @return The visible ol_uids. */ - getVisibleOlUids = () => { - const layers = MapUtil.getAllLayers(this.state.layerGroup, (layer) => { - return !(layer instanceof OlLayerGroup) && layer.getVisible(); - }).filter(this.props.filterFunction); - return layers.map(getUid); + getVisibleOlUids = (): string[] => { + if (!this.state.layerGroup) { + return []; + } + + return MapUtil.getAllLayers(this.state.layerGroup, (layer: OlLayerBase) => { + return !(layer instanceof OlLayerGroup) && layer.getVisible(); + }) + .filter(this.props.filterFunction) + .map(getUid); }; /** @@ -450,9 +448,10 @@ class LayerTree extends React.Component { onCheck(checkedKeys: string[], e: AntTreeNodeCheckedEvent) { const { checked = false } = e; const eventKey = e.node.props.eventKey; - const layer = MapUtil.getLayerByOlUid(this.props.map, eventKey); - - this.setLayerVisibility(layer, checked); + if (eventKey) { + const layer = MapUtil.getLayerByOlUid(this.props.map, eventKey); + this.setLayerVisibility(layer, checked); + } } /** @@ -508,6 +507,9 @@ class LayerTree extends React.Component { * @param e The ant-tree event object for this event. See ant docs. */ onDrop(e: AntTreeNodeDropEvent) { + if (!e.dragNode.props.eventKey || !e.node.props.eventKey) { + return; + } const dragLayer = MapUtil.getLayerByOlUid(this.props.map, e.dragNode.props.eventKey); const dragInfo = MapUtil.getLayerPositionInfo(dragLayer, this.props.map); const dragCollection = dragInfo.groupLayer.getLayers(); diff --git a/src/Legend/Legend.tsx b/src/Legend/Legend.tsx index 26cfafa9dd..d36481e0e0 100644 --- a/src/Legend/Legend.tsx +++ b/src/Legend/Legend.tsx @@ -25,7 +25,7 @@ export interface BaseProps { /** * Optional method that should be called when the image retrieval errors */ - onError?: () => void; + onError?: (e: Error) => void; /** * Optional error message that should be displayed when image retrieval * errors. Will remove the browsers default broken image @@ -120,7 +120,7 @@ export class Legend extends React.Component { /** * onError handler for the rendered img. */ - onError(e) { + onError(e: Error) { Logger.warn(`Image error for legend of "${this.props.layer.get('name')}".`); this.setState({ legendError: true diff --git a/src/Panel/TimeLayerSliderPanel/TimeLayerSliderPanel.tsx b/src/Panel/TimeLayerSliderPanel/TimeLayerSliderPanel.tsx index 71010d0842..980f9031b3 100644 --- a/src/Panel/TimeLayerSliderPanel/TimeLayerSliderPanel.tsx +++ b/src/Panel/TimeLayerSliderPanel/TimeLayerSliderPanel.tsx @@ -172,10 +172,8 @@ export class TimeLayerSliderPanel extends React.Component { - let valueToSet; - if (_isFinite(parseFloat(val))) { - valueToSet = parseFloat(val); - } else { - valueToSet = val; - } + onPlaybackSpeedChange = (val: number | PlaybackSpeedType) => { this.setState({ - playbackSpeed: valueToSet + playbackSpeed: val }, () => { if (this.state.autoPlayActive) { this.autoPlay(true); @@ -487,7 +479,7 @@ export class TimeLayerSliderPanel extends React.Component