@eeacms/volto-n2k 1.0.29 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +11 -3
  2. package/jest-addon.config.js +2 -2
  3. package/package.json +1 -1
  4. package/src/components/manage/Blocks/BubbleChart/Chart.jsx +0 -5
  5. package/src/components/manage/Blocks/CDDACountryProfileMap/Edit.jsx +34 -0
  6. package/src/components/manage/Blocks/CDDACountryProfileMap/View.jsx +208 -0
  7. package/src/components/manage/Blocks/CDDACountryProfileMap/index.js +24 -0
  8. package/src/components/manage/Blocks/CDDACountryProfileMap/schema.js +19 -0
  9. package/src/components/manage/Blocks/CDDACountryProfileMap/style.less +240 -0
  10. package/src/components/manage/Blocks/CddaShape/View.jsx +1 -2
  11. package/src/components/manage/Blocks/ExploreHabitats/View.jsx +1 -2
  12. package/src/components/manage/Blocks/ExploreSites/View.jsx +1 -1
  13. package/src/components/manage/Blocks/ExploreSpecies/View.jsx +1 -1
  14. package/src/components/manage/Blocks/HabitatDistribution/View.jsx +1 -1
  15. package/src/components/manage/Blocks/HabitatProtectedSites/View.jsx +1 -1
  16. package/src/components/manage/Blocks/Landing/Edit.jsx +6 -2
  17. package/src/components/manage/Blocks/N2KCountryProfileMap/Edit.jsx +34 -0
  18. package/src/components/manage/Blocks/N2KCountryProfileMap/View.jsx +165 -0
  19. package/src/components/manage/Blocks/N2KCountryProfileMap/index.js +24 -0
  20. package/src/components/manage/Blocks/N2KCountryProfileMap/schema.js +19 -0
  21. package/src/components/manage/Blocks/N2KCountryProfileMap/style.less +240 -0
  22. package/src/components/manage/Blocks/SiteProtectedSpecies/style.less +1 -1
  23. package/src/components/manage/Blocks/SiteShape/View.jsx +1 -2
  24. package/src/components/manage/Blocks/SiteSpeciesList/View.jsx +1 -2
  25. package/src/components/manage/Blocks/SpeciesClassification/View.jsx +0 -2
  26. package/src/components/manage/Blocks/SpeciesDistribution/View.jsx +1 -1
  27. package/src/components/manage/Blocks/SpeciesProtectedSites/View.jsx +1 -1
  28. package/src/components/manage/Blocks/StackedBarChart/Chart.jsx +0 -6
  29. package/src/components/theme/Sitemap/Sitemap.jsx +1 -2
  30. package/src/helpers.js +3 -3
  31. package/src/index.js +4 -2
@@ -3,9 +3,13 @@ import { connect } from 'react-redux';
3
3
  import { Grid, Button } from 'semantic-ui-react';
4
4
  import { isEmpty } from 'lodash';
5
5
  import EditBlockWrapper from './EditBlockWrapper';
6
- import { BlocksForm, Icon } from '@plone/volto/components';
7
6
  import { emptyBlocksForm } from '@plone/volto/helpers';
8
- import { SidebarPortal, UniversalLink } from '@plone/volto/components';
7
+ import {
8
+ SidebarPortal,
9
+ UniversalLink,
10
+ BlocksForm,
11
+ Icon,
12
+ } from '@plone/volto/components';
9
13
  import InlineForm from '@plone/volto/components/manage/Form/InlineForm';
10
14
  import config from '@plone/volto/registry';
11
15
  import settingsSVG from '@plone/volto/icons/settings.svg';
@@ -0,0 +1,34 @@
1
+ import React, { useMemo } from 'react';
2
+ import { SidebarPortal } from '@plone/volto/components';
3
+ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
4
+ import getSchema from './schema';
5
+ import View from './View';
6
+ import './style.less';
7
+
8
+ const Edit = (props) => {
9
+ const schema = useMemo(() => getSchema(props), [props]);
10
+
11
+ return (
12
+ <>
13
+ <View {...props} mode="edit" />
14
+
15
+ <SidebarPortal selected={props.selected}>
16
+ <BlockDataForm
17
+ schema={schema}
18
+ title={schema.title}
19
+ onChangeField={(id, value) => {
20
+ props.onChangeBlock(props.block, {
21
+ ...props.data,
22
+ [id]: value,
23
+ });
24
+ }}
25
+ onChangeBlock={props.onChangeBlock}
26
+ formData={props.data}
27
+ block={props.block}
28
+ />
29
+ </SidebarPortal>
30
+ </>
31
+ );
32
+ };
33
+
34
+ export default Edit;
@@ -0,0 +1,165 @@
1
+ import React, { useState, useEffect, useMemo, useRef } from 'react';
2
+ import isArray from 'lodash/isArray';
3
+ import isString from 'lodash/isString';
4
+ import Map from '@eeacms/volto-openlayers-map/Map';
5
+ import { Layers, Layer } from '@eeacms/volto-openlayers-map/Layers';
6
+ import { openlayers } from '@eeacms/volto-openlayers-map';
7
+
8
+ let dynamicLayerDefinition = `
9
+ [
10
+ {
11
+ "id":2,
12
+ "source":{
13
+ "type":"mapLayer",
14
+ "mapLayerId":2
15
+ },
16
+ "definitionExpression":"MS = '{country}'",
17
+ "drawingInfo":{
18
+ "renderer":{
19
+ "type":"uniqueValue",
20
+ "field1":"MS",
21
+ "uniqueValueInfos":[{
22
+ "value":"{country}",
23
+ "label":"{country}",
24
+ "symbol":{
25
+ "color":[40,149,136,100],
26
+ "outline":{
27
+ "color":[40,149,136,100],
28
+ "width":0.75,
29
+ "type":"esriSLS",
30
+ "style":"esriSLSSolid"
31
+ },
32
+ "type":"esriSFS",
33
+ "style":"esriSFSSolid"
34
+ }
35
+ }
36
+ ]
37
+ },
38
+ "showLabels":false
39
+ },
40
+ "minScale":0,
41
+ "maxScale":0
42
+ }
43
+ ]`;
44
+
45
+ const View = (props) => {
46
+ const mapRef = useRef();
47
+ const [sources, setSources] = useState([]);
48
+ const [tiles, setTiles] = useState([]);
49
+ const [extent, setExtent] = useState(null);
50
+ const { proj, source } = openlayers;
51
+
52
+ const country = useMemo(() => {
53
+ const query = (props.properties.data_query || []).filter(
54
+ (query) => query.i === 'iso2',
55
+ )[0];
56
+ if (!query) return null;
57
+ const value = query.v;
58
+ if (isArray(value)) {
59
+ return value[0];
60
+ }
61
+ if (isString(value)) {
62
+ return value;
63
+ }
64
+ return null;
65
+ }, [props.properties]);
66
+
67
+ useEffect(() => {
68
+ setTiles([
69
+ new source.XYZ({
70
+ url:
71
+ 'https://server.arcgisonline.com/ArcGIS/rest/services/' +
72
+ 'Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}',
73
+ }),
74
+ new source.XYZ({
75
+ url:
76
+ 'https://server.arcgisonline.com/ArcGIS/rest/services/' +
77
+ 'Elevation/World_Hillshade/MapServer/tile/{z}/{y}/{x}',
78
+ }),
79
+ ]);
80
+ /* eslint-disable-next-line */
81
+ }, []);
82
+
83
+ useEffect(() => {
84
+ setSources([
85
+ new source.ImageArcGISRest({
86
+ ratio: 1,
87
+ params: {
88
+ LAYERS: '2',
89
+ dynamicLayers: dynamicLayerDefinition
90
+ .replace(/(\r\n|\n|\r)/gm, '')
91
+ .replaceAll(' ', '')
92
+ .replaceAll('{country}', country),
93
+ },
94
+ url:
95
+ 'https://bio.discomap.eea.europa.eu/arcgis/rest/services/ProtectedSites/Natura2000Sites/MapServer',
96
+ }),
97
+ ]);
98
+ fetch(
99
+ `https://geoenrich.arcgis.com/arcgis/rest/services/World/geoenrichmentserver/Geoenrichment/countries/${country}?f=pjson`,
100
+ )
101
+ .then(function (response) {
102
+ return response.json();
103
+ })
104
+ .then(async (data) => {
105
+ const { fromExtent } = await import('ol/geom/Polygon');
106
+ if (data && data.countries.length > 0) {
107
+ const countryInfo = data.countries[0];
108
+ const countryExtent = [
109
+ countryInfo.defaultExtent.xmin,
110
+ countryInfo.defaultExtent.ymin,
111
+ countryInfo.defaultExtent.xmax,
112
+ countryInfo.defaultExtent.ymax,
113
+ ];
114
+
115
+ const reprojectedExtent = proj.transformExtent(
116
+ countryExtent,
117
+ 'EPSG:4326',
118
+ 'EPSG:3857',
119
+ );
120
+
121
+ const polygon = fromExtent(reprojectedExtent);
122
+ polygon.scale(1.2);
123
+
124
+ setExtent(polygon.getExtent());
125
+ }
126
+ })
127
+ .catch(function () {
128
+ // handle the error
129
+ setExtent(null);
130
+ });
131
+ /* eslint-disable-next-line */
132
+ }, [country]);
133
+
134
+ return (
135
+ <div className="n2k-country-profile-map">
136
+ <Map
137
+ ref={(map) => {
138
+ if (map) {
139
+ mapRef.current = map;
140
+ }
141
+ }}
142
+ view={{
143
+ center: [0, 0],
144
+ extent: extent || [
145
+ -6319125.804807394,
146
+ 3070702.923644739,
147
+ 9584655.106275197,
148
+ 12091128.659149397,
149
+ ],
150
+ zoom: 2,
151
+ showFullExtent: true,
152
+ }}
153
+ pixelRatio={1}
154
+ >
155
+ <Layers>
156
+ <Layer.Tile source={tiles[1]} zIndex={0} opacity={1} />
157
+ <Layer.Tile source={tiles[0]} zIndex={1} opacity={0.7} />
158
+ <Layer.Image source={sources[0]} zIndex={2} />
159
+ </Layers>
160
+ </Map>
161
+ </div>
162
+ );
163
+ };
164
+
165
+ export default View;
@@ -0,0 +1,24 @@
1
+ import worldSVG from '@plone/volto/icons/world.svg';
2
+ import Edit from './Edit';
3
+ import View from './View';
4
+
5
+ export default (config) => {
6
+ config.blocks.blocksConfig.n2k_country_profile_map = {
7
+ id: 'n2k_country_profile_map',
8
+ title: 'N2K country profile map',
9
+ icon: worldSVG,
10
+ group: 'custom_blocks',
11
+ edit: Edit,
12
+ view: View,
13
+ restricted: false,
14
+ mostUsed: false,
15
+ sidebarTab: 1,
16
+ blocks: {},
17
+ security: {
18
+ addPermission: [],
19
+ view: [],
20
+ },
21
+ blockHasOwnFocusManagement: false,
22
+ };
23
+ return config;
24
+ };
@@ -0,0 +1,19 @@
1
+ const getSchema = () => {
2
+ return {
3
+ title: 'N2K country profile map',
4
+
5
+ fieldsets: [
6
+ {
7
+ id: 'default',
8
+ title: 'Default',
9
+ fields: [],
10
+ },
11
+ ],
12
+
13
+ properties: {},
14
+
15
+ required: [],
16
+ };
17
+ };
18
+
19
+ export default getSchema;
@@ -0,0 +1,240 @@
1
+ @type: extra;
2
+ @element: custom;
3
+
4
+ @import (multiple, reference, optional) '../../theme.config';
5
+
6
+ @import 'swiper/swiper.less';
7
+
8
+ @{view} {
9
+ .species-banner-details {
10
+ &::before {
11
+ .full-width();
12
+ position: absolute;
13
+ z-index: -1;
14
+ }
15
+
16
+ .carousel {
17
+ .half-width(960px);
18
+
19
+ @media @mobile {
20
+ width: calc(@pageFullWidth - 2rem) !important;
21
+ }
22
+ }
23
+ }
24
+ }
25
+
26
+ @{edit} {
27
+ .species-banner-details {
28
+ &::before {
29
+ .full-width-edit();
30
+ position: absolute;
31
+ z-index: -1;
32
+ }
33
+
34
+ .carousel {
35
+ .half-width-edit(960px);
36
+
37
+ @media @mobile {
38
+ width: calc(@pageFullWidthEdit - 2rem) !important;
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ div#view .species-banner-details .ui.container > * {
45
+ margin-top: 0;
46
+ margin-bottom: 0;
47
+ }
48
+
49
+ .species-banner-details {
50
+ position: relative;
51
+ padding: 0;
52
+ margin-bottom: 1rem;
53
+ color: #fff !important;
54
+ font-family: 'RajdhaniB', 'Helvetica Neue', Arial, Helvetica, sans-serif;
55
+
56
+ &::before {
57
+ display: block;
58
+ height: 100%;
59
+ background-color: #00a390;
60
+ content: '';
61
+ }
62
+
63
+ .species-details {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: space-between;
67
+
68
+ .species-metadata {
69
+ display: flex;
70
+ width: 50%;
71
+ height: 320px !important;
72
+ flex: 0 0 50%;
73
+ flex-direction: column;
74
+ justify-content: center;
75
+ word-wrap: break-word;
76
+
77
+ .name {
78
+ margin-top: 0 !important;
79
+ margin-bottom: 0 !important;
80
+ color: #fff !important;
81
+ font-family: inherit;
82
+
83
+ font-family: RajdhaniB, 'Helvetica Neue', Arial, Helvetica, sans-serif;
84
+ font-size: 54px;
85
+ line-height: 54px;
86
+ text-transform: uppercase;
87
+
88
+ > * {
89
+ font-family: RajdhaniB, 'Helvetica Neue', Arial, Helvetica, sans-serif;
90
+ }
91
+ }
92
+
93
+ h3 {
94
+ margin-top: 0 !important;
95
+ margin-bottom: 0 !important;
96
+ color: #fff !important;
97
+ font-family: inherit;
98
+ font-size: 32px;
99
+ line-height: 32px;
100
+ text-transform: uppercase;
101
+ }
102
+
103
+ h4 {
104
+ margin-top: 0 !important;
105
+ margin-bottom: 0 !important;
106
+ color: #fff !important;
107
+ font-family: inherit;
108
+ font-size: 32px;
109
+ line-height: 32px;
110
+ text-transform: uppercase;
111
+ }
112
+
113
+ .info,
114
+ .info > * {
115
+ font-family: 'RajdhaniR', 'Helvetica Neue', Arial, Helvetica, sans-serif;
116
+ font-size: 18px;
117
+ font-weight: 600;
118
+ line-height: 18px;
119
+
120
+ a {
121
+ color: #fff;
122
+
123
+ &:hover {
124
+ color: #fff;
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ .carousel {
131
+ display: flex;
132
+ height: 320px;
133
+
134
+ p {
135
+ font-family: 'RajdhaniB', 'Helvetica Neue', Arial, Helvetica, sans-serif;
136
+ }
137
+
138
+ img {
139
+ width: 100%;
140
+ }
141
+
142
+ &.one-slide .swiper {
143
+ width: 100%;
144
+ }
145
+
146
+ &.two-slides .swiper {
147
+ width: 50%;
148
+
149
+ @media @mobile {
150
+ width: 100%;
151
+
152
+ &.preview.preview-one {
153
+ display: none;
154
+ }
155
+ }
156
+ }
157
+
158
+ &.three-slides .swiper {
159
+ width: 33.33%;
160
+
161
+ @media @tablet {
162
+ width: 50%;
163
+
164
+ &.preview.preview-two {
165
+ display: none;
166
+ }
167
+ }
168
+
169
+ @media @mobile {
170
+ width: 100%;
171
+
172
+ &.preview.preview-two,
173
+ &.preview.preview-one {
174
+ display: none;
175
+ }
176
+ }
177
+ }
178
+
179
+ .swiper {
180
+ &.preview .swiper-slide {
181
+ filter: grayscale(100%);
182
+ }
183
+
184
+ img {
185
+ display: block;
186
+ width: 100%;
187
+ height: 100%;
188
+ object-fit: cover;
189
+ }
190
+ }
191
+
192
+ .arrows {
193
+ position: absolute;
194
+ z-index: 2;
195
+ bottom: 0;
196
+ left: 0;
197
+ display: flex;
198
+ width: 100%;
199
+ align-items: center;
200
+
201
+ button {
202
+ padding: 0;
203
+ border: none;
204
+ margin: 0;
205
+ background-color: transparent;
206
+ }
207
+
208
+ .icon {
209
+ display: block;
210
+ width: 32px;
211
+ background-color: #fff;
212
+ cursor: pointer;
213
+ }
214
+
215
+ p {
216
+ overflow: hidden;
217
+ width: calc(100% - 64px);
218
+ padding: 0.25rem 0.25rem 0.25rem 1rem;
219
+ margin: 0;
220
+ backdrop-filter: brightness(0.5);
221
+ text-overflow: ellipsis;
222
+ white-space: nowrap;
223
+ }
224
+ }
225
+ }
226
+
227
+ @media @mobile {
228
+ flex-flow: column;
229
+
230
+ .species-metadata {
231
+ width: 100%;
232
+ margin-top: 2rem;
233
+ }
234
+
235
+ .carousel {
236
+ margin: 0 1rem 1rem 1rem;
237
+ }
238
+ }
239
+ }
240
+ }
@@ -13,7 +13,7 @@ div#view .ui.container.species-wrapper > * {
13
13
  background-position: center;
14
14
  background-repeat: no-repeat;
15
15
  background-size: cover;
16
- font-family: 'RajdhaniB';
16
+ font-family: 'RajdhaniB', sans-serif;
17
17
 
18
18
  .species-container {
19
19
  position: absolute;
@@ -60,7 +60,6 @@ const View = (props) => {
60
60
  /* eslint-disable-next-line */
61
61
  }, [vectorSource, site_code?.[0]]);
62
62
 
63
- if (__SERVER__ || !vectorSource) return '';
64
63
  return (
65
64
  <div className="site-shape-wrapper full-width">
66
65
  <div className="site-shape">
@@ -69,9 +68,9 @@ const View = (props) => {
69
68
  center: proj.fromLonLat([20, 50]),
70
69
  showFullExtent: true,
71
70
  zoom: 5,
71
+ ...(options.extent ? { extent: options.extent } : {}),
72
72
  }}
73
73
  pixelRatio={1}
74
- {...options}
75
74
  >
76
75
  <Layers>
77
76
  <Layer.Tile source={tileWMSSources[0]} zIndex={0} />
@@ -1,9 +1,8 @@
1
1
  import React from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
  import { Container, Pagination, Grid } from 'semantic-ui-react';
4
- import { getObjectByIndex } from '@eeacms/volto-n2k/helpers';
4
+ import { getObjectByIndex, photoPlaceholders } from '@eeacms/volto-n2k/helpers';
5
5
  import { Filters } from './Filters';
6
- import { photoPlaceholders } from '@eeacms/volto-n2k/helpers';
7
6
  import { getPopulationString, getLabelString } from './utils';
8
7
 
9
8
  import './style.less';
@@ -1,9 +1,7 @@
1
1
  import React from 'react';
2
- // import { UniversalLink } from '@plone/volto/components';
3
2
  import './style.less';
4
3
 
5
4
  const View = (props) => {
6
- // const provider_data = props.provider_data || {};
7
5
  return (
8
6
  <div className="species-classification">
9
7
  {props.mode === 'edit' ? <p>Species classification</p> : ''}
@@ -74,9 +74,9 @@ const View = (props) => {
74
74
  center: proj.fromLonLat([20, 50]),
75
75
  showFullExtent: true,
76
76
  zoom: 5,
77
+ ...(options.extent ? { extent: options.extent } : {}),
77
78
  }}
78
79
  pixelRatio={1}
79
- {...options}
80
80
  >
81
81
  <Layers>
82
82
  <Layer.Tile source={tileWMSSources[0]} zIndex={0} />
@@ -70,9 +70,9 @@ const View = (props) => {
70
70
  center: proj.fromLonLat([20, 50]),
71
71
  showFullExtent: true,
72
72
  zoom: 5,
73
+ ...(options.extent ? { extent: options.extent } : {}),
73
74
  }}
74
75
  pixelRatio={1}
75
- {...options}
76
76
  >
77
77
  <Layers>
78
78
  <Layer.Tile source={tileWMSSources[0]} zIndex={0} />
@@ -26,18 +26,12 @@ function Chart(props) {
26
26
  } = props;
27
27
 
28
28
  useEffect(() => {
29
- // dispose();
30
29
  if (xDomain.length && yDomain.length) {
31
30
  makeAxis();
32
31
  }
33
32
  /* eslint-disable-next-line */
34
33
  }, [JSON.stringify(xDomain), JSON.stringify(yDomain)]);
35
34
 
36
- // const dispose = () => {
37
- // const svg = select(svgRef.current);
38
- // svg.selectAll('*').remove();
39
- // };
40
-
41
35
  const makeAxis = () => {
42
36
  const svg = select(svgRef.current);
43
37
  // Scales
@@ -8,10 +8,9 @@ import PropTypes from 'prop-types';
8
8
  import { compose } from 'redux';
9
9
  import { connect } from 'react-redux';
10
10
  import { matchPath } from 'react-router';
11
- import { asyncConnect } from '@plone/volto/helpers';
11
+ import { asyncConnect, Helmet } from '@plone/volto/helpers';
12
12
  import { defineMessages, injectIntl } from 'react-intl';
13
13
  import { Container } from 'semantic-ui-react';
14
- import { Helmet } from '@plone/volto/helpers';
15
14
  import { Link } from 'react-router-dom';
16
15
  import config from '@plone/volto/registry';
17
16
  import { withLocalStorage } from '@eeacms/volto-n2k/hocs';
package/src/helpers.js CHANGED
@@ -53,12 +53,12 @@ export function sortBy(obj, property, order = 'ASC') {
53
53
  }
54
54
 
55
55
  export const componentToHex = (c) => {
56
- var hex = parseInt(c).toString(16);
56
+ const hex = parseInt(c).toString(16);
57
57
  return hex.length === 1 ? '0' + hex : hex;
58
58
  };
59
59
 
60
60
  function hexToRgb(hex) {
61
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
61
+ let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
62
62
  return result
63
63
  ? [
64
64
  parseInt(result[1], 16),
@@ -86,7 +86,7 @@ export const getContrastColor = (rgb) => {
86
86
  };
87
87
 
88
88
  export const adjustBrightness = (col, amt) => {
89
- var usePound = false;
89
+ let usePound = false;
90
90
 
91
91
  if (col.includes('rgb(')) {
92
92
  const rgb = col
package/src/index.js CHANGED
@@ -42,13 +42,13 @@ import installSpeciesDistribution from './components/manage/Blocks/SpeciesDistri
42
42
  import installSpeciesProtectedSites from './components/manage/Blocks/SpeciesProtectedSites';
43
43
  import installStackedBarChart from './components/manage/Blocks/StackedBarChart';
44
44
  import installTilesImages from './components/manage/Blocks/TilesImages';
45
+ import installN2KCountryProfileMap from './components/manage/Blocks/N2KCountryProfileMap';
46
+ import installCDDACountryProfileMap from './components/manage/Blocks/CDDACountryProfileMap';
45
47
 
46
48
  import { LINK } from '@plone/volto-slate/constants';
47
49
 
48
50
  import { gridSizes, variants } from './grid';
49
51
 
50
- // import './less/styles.less';
51
-
52
52
  Array.prototype.sortByProperty = function (property, order = 'ASC') {
53
53
  return this.sort((a, b) => {
54
54
  if (a[property] < b[property]) return order === 'ASC' ? -1 : 1;
@@ -158,6 +158,8 @@ const applyConfig = (config) => {
158
158
  installSpeciesProtectedSites,
159
159
  installStackedBarChart,
160
160
  installTilesImages,
161
+ installN2KCountryProfileMap,
162
+ installCDDACountryProfileMap,
161
163
  ].reduce((acc, apply) => apply(acc), config);
162
164
  };
163
165