@eeacms/volto-n2k 1.0.24 → 1.0.26
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.
- package/CHANGELOG.md +12 -4
- package/package.json +1 -1
- package/src/components/manage/Blocks/CarouselHorizontal/HorizontalView.jsx +24 -48
- package/src/components/manage/Blocks/HabitatsBanner/Edit.jsx +1 -16
- package/src/components/manage/Blocks/HabitatsBanner/View.jsx +47 -34
- package/src/components/manage/Blocks/HabitatsBanner/schema.js +13 -52
- package/src/components/manage/Blocks/NavigationAnchors/styles.less +0 -2
- package/src/components/manage/Blocks/SpeciesBanner/Edit.jsx +34 -0
- package/src/components/manage/Blocks/SpeciesBanner/View.jsx +84 -19
- package/src/components/manage/Blocks/SpeciesBanner/index.js +18 -11
- package/src/components/manage/Blocks/SpeciesBanner/schema.js +24 -3
- package/src/components/theme/AppExtras/index.js +0 -5
- package/src/customizations/@eeacms/volto-group-block/components/manage/Blocks/Group/EditBlockWrapper.jsx +9 -2
- package/src/helpers.js +5 -0
- package/src/components/theme/AppExtras/CopyPaste/CopyPaste.jsx +0 -176
- package/src/components/theme/AppExtras/CopyPaste/index.js +0 -3
- package/src/components/theme/AppExtras/CopyPaste/style.less +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -4,16 +4,24 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [1.0.26](https://github.com/eea/volto-n2k/compare/1.0.25...1.0.26) - 29 May 2023
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix: make carousel work [Miu Razvan - [`d46cb68`](https://github.com/eea/volto-n2k/commit/d46cb68c8416d9dac1c1248b6b5647983e1c9fa1)]
|
|
12
|
+
|
|
13
|
+
### [1.0.25](https://github.com/eea/volto-n2k/compare/1.0.24...1.0.25) - 26 April 2023
|
|
14
|
+
|
|
15
|
+
#### :rocket: New Features
|
|
16
|
+
|
|
17
|
+
- feat(Group/EditBlockWrapper.jsx): add support for custom styles in group block editing mode [Miu Razvan - [`97daa39`](https://github.com/eea/volto-n2k/commit/97daa39e8b3bb7a8c04b357706bb79126c2faecf)]
|
|
18
|
+
|
|
7
19
|
### [1.0.24](https://github.com/eea/volto-n2k/compare/1.0.23...1.0.24) - 26 April 2023
|
|
8
20
|
|
|
9
21
|
#### :house: Internal changes
|
|
10
22
|
|
|
11
23
|
- style: fix margin-bottom position in habitat-banner-details and species-banner-details [Miu Razvan - [`4e78d69`](https://github.com/eea/volto-n2k/commit/4e78d6927829f18a91f6b409ee670b01d538dba6)]
|
|
12
24
|
|
|
13
|
-
#### :hammer_and_wrench: Others
|
|
14
|
-
|
|
15
|
-
- update [Miu Razvan - [`4347ffc`](https://github.com/eea/volto-n2k/commit/4347ffc93743dbc9d5514fe1f7d6e39b73abd0c8)]
|
|
16
|
-
- update [Miu Razvan - [`4e8317f`](https://github.com/eea/volto-n2k/commit/4e8317ffbe459d5bc15092fa9f43255707a77e37)]
|
|
17
25
|
### [1.0.23](https://github.com/eea/volto-n2k/compare/1.0.22...1.0.23) - 12 April 2023
|
|
18
26
|
|
|
19
27
|
#### :house: Internal changes
|
package/package.json
CHANGED
|
@@ -17,7 +17,8 @@ const Slider = loadable(() => import('react-slick'));
|
|
|
17
17
|
|
|
18
18
|
const Dots = (props) => {
|
|
19
19
|
const { activeTab = null, tabsList = [], slider = {} } = props;
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
return slider && tabsList.length > 1 ? (
|
|
21
22
|
<div className="slick-dots-wrapper">
|
|
22
23
|
<div className="slick-line" />
|
|
23
24
|
<ul className={cx('slick-dots ui container', props.uiContainer)}>
|
|
@@ -30,8 +31,8 @@ const Dots = (props) => {
|
|
|
30
31
|
aria-label={`Select slide ${index + 1}`}
|
|
31
32
|
tabIndex={0}
|
|
32
33
|
onClick={() => {
|
|
33
|
-
if (slider
|
|
34
|
-
slider.
|
|
34
|
+
if (slider) {
|
|
35
|
+
slider.slickGoTo(index);
|
|
35
36
|
}
|
|
36
37
|
}}
|
|
37
38
|
/>
|
|
@@ -49,7 +50,7 @@ const ArrowsGroup = (props) => {
|
|
|
49
50
|
const currentSlide = tabsList.indexOf(activeTab);
|
|
50
51
|
const slideCount = tabsList.length;
|
|
51
52
|
|
|
52
|
-
return slider
|
|
53
|
+
return slider ? (
|
|
53
54
|
<div
|
|
54
55
|
className={cx({
|
|
55
56
|
'slick-arrows': true,
|
|
@@ -61,8 +62,8 @@ const ArrowsGroup = (props) => {
|
|
|
61
62
|
aria-label="Previous slide"
|
|
62
63
|
className="slick-arrow slick-prev"
|
|
63
64
|
onClick={() => {
|
|
64
|
-
if (slider
|
|
65
|
-
slider.
|
|
65
|
+
if (slider) {
|
|
66
|
+
slider.slickPrev();
|
|
66
67
|
}
|
|
67
68
|
}}
|
|
68
69
|
tabIndex={0}
|
|
@@ -77,8 +78,8 @@ const ArrowsGroup = (props) => {
|
|
|
77
78
|
aria-label="Next slide"
|
|
78
79
|
className="slick-arrow slick-next"
|
|
79
80
|
onClick={() => {
|
|
80
|
-
if (slider
|
|
81
|
-
slider.
|
|
81
|
+
if (slider) {
|
|
82
|
+
slider.slickNext();
|
|
82
83
|
}
|
|
83
84
|
}}
|
|
84
85
|
tabIndex={0}
|
|
@@ -100,9 +101,9 @@ const ArrowsGroup = (props) => {
|
|
|
100
101
|
};
|
|
101
102
|
|
|
102
103
|
const View = (props) => {
|
|
103
|
-
const slider = React.useRef(null);
|
|
104
104
|
const img = React.useRef(null);
|
|
105
|
-
|
|
105
|
+
const sliderUp = React.useRef(null);
|
|
106
|
+
const [slider, setSlider] = React.useState(null);
|
|
106
107
|
const [hashlinkOnMount, setHashlinkOnMount] = React.useState(false);
|
|
107
108
|
const blockId = props.id;
|
|
108
109
|
const {
|
|
@@ -134,28 +135,12 @@ const View = (props) => {
|
|
|
134
135
|
},
|
|
135
136
|
};
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// {index === 0 ? (
|
|
144
|
-
// <div
|
|
145
|
-
// className="divider"
|
|
146
|
-
// style={{ height: `${imgHeight - 80}px` }}
|
|
147
|
-
// />
|
|
148
|
-
// ) : (
|
|
149
|
-
// ''
|
|
150
|
-
// )}
|
|
151
|
-
// </React.Fragment>
|
|
152
|
-
// ),
|
|
153
|
-
// };
|
|
154
|
-
// });
|
|
155
|
-
|
|
156
|
-
// const updateImageHeight = () => {
|
|
157
|
-
// setImgHeight(img.current?.height || 0);
|
|
158
|
-
// };
|
|
138
|
+
React.useEffect(() => {
|
|
139
|
+
return () => {
|
|
140
|
+
setSlider(null);
|
|
141
|
+
sliderUp.current = false;
|
|
142
|
+
};
|
|
143
|
+
}, []);
|
|
159
144
|
|
|
160
145
|
React.useEffect(() => {
|
|
161
146
|
const urlHash = props.location.hash.substring(1) || '';
|
|
@@ -178,7 +163,7 @@ const View = (props) => {
|
|
|
178
163
|
parent
|
|
179
164
|
) {
|
|
180
165
|
if (activeTabIndex !== index) {
|
|
181
|
-
slider.
|
|
166
|
+
slider.slickGoTo(index);
|
|
182
167
|
}
|
|
183
168
|
props.scrollToTarget(parent, offsetHeight);
|
|
184
169
|
} else if (id === parentId && parent) {
|
|
@@ -205,25 +190,16 @@ const View = (props) => {
|
|
|
205
190
|
};
|
|
206
191
|
});
|
|
207
192
|
|
|
208
|
-
// React.useEffect(() => {
|
|
209
|
-
// updateImageHeight();
|
|
210
|
-
// img.current.onload = () => {
|
|
211
|
-
// updateImageHeight();
|
|
212
|
-
// };
|
|
213
|
-
// window.addEventListener('resize', updateImageHeight);
|
|
214
|
-
// return () => {
|
|
215
|
-
// window.removeEventListener('resize', updateImageHeight);
|
|
216
|
-
// };
|
|
217
|
-
// /* eslint-disable-next-line */
|
|
218
|
-
// }, []);
|
|
219
|
-
|
|
220
|
-
// console.log('HERE RERENDER');
|
|
221
|
-
|
|
222
193
|
return (
|
|
223
194
|
<>
|
|
224
195
|
<Slider
|
|
225
196
|
{...settings}
|
|
226
|
-
ref={
|
|
197
|
+
ref={(sliderRef) => {
|
|
198
|
+
if (!slider && !sliderUp.current) {
|
|
199
|
+
setSlider(sliderRef);
|
|
200
|
+
sliderUp.current = true;
|
|
201
|
+
}
|
|
202
|
+
}}
|
|
227
203
|
className={cx(uiContainer, 'tabs-accessibility')}
|
|
228
204
|
accessibility={true}
|
|
229
205
|
afterChange={() => {
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
-
import { compose } from 'redux';
|
|
3
2
|
import { SidebarPortal } from '@plone/volto/components';
|
|
4
3
|
import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
|
|
5
|
-
import { VisibilitySensor } from '@eeacms/volto-datablocks/components';
|
|
6
|
-
import { connectToProviderData } from '@eeacms/volto-datablocks/hocs';
|
|
7
4
|
import getSchema from './schema';
|
|
8
5
|
import View from './View';
|
|
9
6
|
import './style.less';
|
|
@@ -34,16 +31,4 @@ const Edit = (props) => {
|
|
|
34
31
|
);
|
|
35
32
|
};
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
connectToProviderData((props) => ({
|
|
39
|
-
provider_url: props.data?.provider_url,
|
|
40
|
-
})),
|
|
41
|
-
)(Edit);
|
|
42
|
-
|
|
43
|
-
export default (props) => {
|
|
44
|
-
return (
|
|
45
|
-
<VisibilitySensor>
|
|
46
|
-
<EditWrapper {...props} />
|
|
47
|
-
</VisibilitySensor>
|
|
48
|
-
);
|
|
49
|
-
};
|
|
34
|
+
export default Edit;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React, { useRef, useState } from 'react';
|
|
1
|
+
import React, { useRef, useMemo, useState } from 'react';
|
|
2
2
|
import { compose } from 'redux';
|
|
3
3
|
import cx from 'classnames';
|
|
4
4
|
import loadable from '@loadable/component';
|
|
5
5
|
import { Icon } from '@plone/volto/components';
|
|
6
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
6
7
|
import { VisibilitySensor } from '@eeacms/volto-datablocks/components';
|
|
7
8
|
import { connectToMultipleProviders } from '@eeacms/volto-datablocks/hocs';
|
|
9
|
+
import { replaceQueryParam } from '@eeacms/volto-n2k/helpers';
|
|
8
10
|
import arrowLeft from '@eeacms/volto-n2k/icons/arrow-left.svg';
|
|
9
11
|
import arrowRight from '@eeacms/volto-n2k/icons/arrow-right.svg';
|
|
10
12
|
import './style.less';
|
|
@@ -12,34 +14,39 @@ import './style.less';
|
|
|
12
14
|
const SwiperLoader = loadable.lib(() => import('swiper'));
|
|
13
15
|
const SwiperReactLoader = loadable.lib(() => import('swiper/react'));
|
|
14
16
|
|
|
17
|
+
const getSource = (source) => {
|
|
18
|
+
let parsedSource = replaceQueryParam(source, 'x', 300);
|
|
19
|
+
parsedSource = replaceQueryParam(parsedSource, 'y', 300);
|
|
20
|
+
|
|
21
|
+
return parsedSource;
|
|
22
|
+
};
|
|
23
|
+
|
|
15
24
|
const _View = (props) => {
|
|
16
25
|
const swiperEl = useRef();
|
|
17
26
|
const previewEl = useRef();
|
|
18
27
|
const [activeSlide, setActiveSlide] = useState(0);
|
|
19
|
-
const
|
|
20
|
-
const
|
|
28
|
+
const habitat_provider = flattenToAppURL(props.data.habitat_provider);
|
|
29
|
+
const habitat_pictures_provider = flattenToAppURL(
|
|
30
|
+
props.data.habitat_pictures_provider,
|
|
31
|
+
);
|
|
32
|
+
const habitat = props.providers_data?.[habitat_provider] || {};
|
|
21
33
|
const habitat_pictures =
|
|
22
|
-
props.providers_data?.[
|
|
34
|
+
props.providers_data?.[habitat_pictures_provider] || {};
|
|
23
35
|
|
|
24
|
-
const {
|
|
25
|
-
|
|
26
|
-
// habitat_description = [],
|
|
27
|
-
// habitat_type = [],
|
|
28
|
-
// number_countries = [],
|
|
29
|
-
// number_sites = [],
|
|
30
|
-
scientific_name,
|
|
31
|
-
} = habitat;
|
|
36
|
+
const { code_2000 = [], scientific_name = [] } = habitat;
|
|
37
|
+
const { attribution_copyright = [] } = habitat_pictures;
|
|
32
38
|
|
|
33
39
|
const pictures = habitat_pictures?.['WebURL'] || [];
|
|
34
|
-
const pictures_length =
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
const pictures_length = useMemo(
|
|
41
|
+
() => pictures.filter((picture) => !!picture)?.length,
|
|
42
|
+
[pictures],
|
|
43
|
+
);
|
|
37
44
|
|
|
38
|
-
if (!
|
|
39
|
-
return 'Habitat banner block
|
|
45
|
+
if (!habitat_provider && props.mode === 'edit') {
|
|
46
|
+
return 'Habitat banner block, habitat provider undefined';
|
|
40
47
|
}
|
|
41
|
-
if (!
|
|
42
|
-
|
|
48
|
+
if (!habitat_pictures_provider && props.mode === 'edit') {
|
|
49
|
+
return 'Habitat banner block, habitat pictures provider undefined';
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
return (
|
|
@@ -50,14 +57,6 @@ const _View = (props) => {
|
|
|
50
57
|
<p className="info">
|
|
51
58
|
Habitats Directive Annex I code {code_2000[0]}
|
|
52
59
|
</p>
|
|
53
|
-
{/* {number_sites[0] && (
|
|
54
|
-
<>
|
|
55
|
-
<h3 style={{ marginBottom: '0.15rem' }}>{number_sites[0]}</h3>
|
|
56
|
-
<h4 className="radjhan-normal">
|
|
57
|
-
NATURA 2000 SITES PROTECTING THIS HABITAT
|
|
58
|
-
</h4>
|
|
59
|
-
</>
|
|
60
|
-
)} */}
|
|
61
60
|
</div>
|
|
62
61
|
{pictures_length > 0 && (
|
|
63
62
|
<div
|
|
@@ -108,9 +107,9 @@ const _View = (props) => {
|
|
|
108
107
|
size="32px"
|
|
109
108
|
/>
|
|
110
109
|
</button>
|
|
111
|
-
{
|
|
112
|
-
{
|
|
113
|
-
|
|
110
|
+
{!!attribution_copyright[activeSlide] && (
|
|
111
|
+
<p>{attribution_copyright[activeSlide]}</p>
|
|
112
|
+
)}
|
|
114
113
|
</div>
|
|
115
114
|
<SwiperLoader>
|
|
116
115
|
{() => {
|
|
@@ -130,7 +129,10 @@ const _View = (props) => {
|
|
|
130
129
|
>
|
|
131
130
|
{pictures.map((source, index) => (
|
|
132
131
|
<SwiperSlide>
|
|
133
|
-
<img
|
|
132
|
+
<img
|
|
133
|
+
src={getSource(source)}
|
|
134
|
+
alt={pictures[index]}
|
|
135
|
+
/>
|
|
134
136
|
</SwiperSlide>
|
|
135
137
|
))}
|
|
136
138
|
</Swiper>
|
|
@@ -150,7 +152,10 @@ const _View = (props) => {
|
|
|
150
152
|
>
|
|
151
153
|
{pictures.map((source, index) => (
|
|
152
154
|
<SwiperSlide>
|
|
153
|
-
<img
|
|
155
|
+
<img
|
|
156
|
+
src={getSource(source)}
|
|
157
|
+
alt={pictures[index]}
|
|
158
|
+
/>
|
|
154
159
|
</SwiperSlide>
|
|
155
160
|
))}
|
|
156
161
|
</Swiper>
|
|
@@ -171,7 +176,10 @@ const _View = (props) => {
|
|
|
171
176
|
>
|
|
172
177
|
{pictures.map((source, index) => (
|
|
173
178
|
<SwiperSlide>
|
|
174
|
-
<img
|
|
179
|
+
<img
|
|
180
|
+
src={getSource(source)}
|
|
181
|
+
alt={pictures[index]}
|
|
182
|
+
/>
|
|
175
183
|
</SwiperSlide>
|
|
176
184
|
))}
|
|
177
185
|
</Swiper>
|
|
@@ -192,7 +200,12 @@ const _View = (props) => {
|
|
|
192
200
|
|
|
193
201
|
const View = compose(
|
|
194
202
|
connectToMultipleProviders((props) => ({
|
|
195
|
-
providers:
|
|
203
|
+
providers: [
|
|
204
|
+
{
|
|
205
|
+
provider_url: props.data?.habitat_provider,
|
|
206
|
+
},
|
|
207
|
+
{ provider_url: props.data?.habitat_pictures_provider },
|
|
208
|
+
],
|
|
196
209
|
})),
|
|
197
210
|
)(_View);
|
|
198
211
|
|
|
@@ -1,60 +1,26 @@
|
|
|
1
|
-
const
|
|
2
|
-
title: 'Source',
|
|
3
|
-
|
|
4
|
-
fieldsets: [
|
|
5
|
-
{
|
|
6
|
-
id: 'default',
|
|
7
|
-
title: 'Default',
|
|
8
|
-
fields: ['source', 'source_link'],
|
|
9
|
-
},
|
|
10
|
-
],
|
|
11
|
-
|
|
12
|
-
properties: {
|
|
13
|
-
source: {
|
|
14
|
-
type: 'string',
|
|
15
|
-
title: 'Source',
|
|
16
|
-
},
|
|
17
|
-
source_link: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
title: 'Link',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
required: ['source'],
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const DataProvidersSchema = {
|
|
27
|
-
title: 'Data provider',
|
|
28
|
-
fieldsets: [{ id: 'default', title: 'Default', fields: ['provider_url'] }],
|
|
29
|
-
properties: {
|
|
30
|
-
provider_url: {
|
|
31
|
-
title: 'Provider url',
|
|
32
|
-
widget: 'object_by_path',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const getSchema = (props) => {
|
|
1
|
+
const getSchema = () => {
|
|
38
2
|
return {
|
|
39
3
|
title: 'Habitats Banner',
|
|
40
4
|
fieldsets: [
|
|
41
5
|
{
|
|
42
6
|
id: 'default',
|
|
43
7
|
title: 'Default',
|
|
44
|
-
fields: [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
fields: ['sources'],
|
|
8
|
+
fields: [
|
|
9
|
+
'habitat_provider',
|
|
10
|
+
'habitat_pictures_provider',
|
|
11
|
+
'allowedParams',
|
|
12
|
+
],
|
|
50
13
|
},
|
|
51
14
|
],
|
|
52
15
|
|
|
53
16
|
properties: {
|
|
54
|
-
|
|
55
|
-
title: '
|
|
56
|
-
|
|
57
|
-
|
|
17
|
+
habitat_provider: {
|
|
18
|
+
title: 'Habitat provider',
|
|
19
|
+
widget: 'url',
|
|
20
|
+
},
|
|
21
|
+
habitat_pictures_provider: {
|
|
22
|
+
title: 'Habitat pictures provider',
|
|
23
|
+
widget: 'url',
|
|
58
24
|
},
|
|
59
25
|
allowedParams: {
|
|
60
26
|
title: 'Allowed params',
|
|
@@ -64,11 +30,6 @@ const getSchema = (props) => {
|
|
|
64
30
|
choices: [],
|
|
65
31
|
},
|
|
66
32
|
},
|
|
67
|
-
sources: {
|
|
68
|
-
title: 'Sources',
|
|
69
|
-
schema: SourceSchema,
|
|
70
|
-
widget: 'object_list',
|
|
71
|
-
},
|
|
72
33
|
},
|
|
73
34
|
|
|
74
35
|
required: ['url'],
|
|
@@ -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;
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/* eslint-disable react/jsx-no-target-blank */
|
|
2
|
-
import React, { useRef, useState } from 'react';
|
|
2
|
+
import React, { useRef, useMemo, useState } from 'react';
|
|
3
|
+
import { compose } from 'redux';
|
|
3
4
|
import cx from 'classnames';
|
|
4
5
|
import loadable from '@loadable/component';
|
|
6
|
+
import { flattenToAppURL } from '@plone/volto/helpers';
|
|
5
7
|
import { Icon } from '@plone/volto/components';
|
|
8
|
+
import { VisibilitySensor } from '@eeacms/volto-datablocks/components';
|
|
9
|
+
import { connectToMultipleProviders } from '@eeacms/volto-datablocks/hocs';
|
|
10
|
+
import { replaceQueryParam } from '@eeacms/volto-n2k/helpers';
|
|
6
11
|
import arrowLeft from '@eeacms/volto-n2k/icons/arrow-left.svg';
|
|
7
12
|
import arrowRight from '@eeacms/volto-n2k/icons/arrow-right.svg';
|
|
8
13
|
import './style.less';
|
|
@@ -10,26 +15,56 @@ import './style.less';
|
|
|
10
15
|
const SwiperLoader = loadable.lib(() => import('swiper'));
|
|
11
16
|
const SwiperReactLoader = loadable.lib(() => import('swiper/react'));
|
|
12
17
|
|
|
13
|
-
const
|
|
18
|
+
const getSource = (source) => {
|
|
19
|
+
let parsedSource = replaceQueryParam(source, 'x', 300);
|
|
20
|
+
parsedSource = replaceQueryParam(parsedSource, 'y', 300);
|
|
21
|
+
|
|
22
|
+
return parsedSource;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const _View = (props) => {
|
|
14
26
|
const swiperEl = useRef();
|
|
15
27
|
const previewEl = useRef();
|
|
16
28
|
const [activeSlide, setActiveSlide] = useState(0);
|
|
17
|
-
const
|
|
29
|
+
const species_provider = flattenToAppURL(props.data.species_provider);
|
|
30
|
+
const species_pictures_provider = flattenToAppURL(
|
|
31
|
+
props.data.species_pictures_provider,
|
|
32
|
+
);
|
|
33
|
+
const species = props.providers_data?.[species_provider] || {};
|
|
34
|
+
const species_pictures =
|
|
35
|
+
props.providers_data?.[species_pictures_provider] || {};
|
|
36
|
+
|
|
18
37
|
const {
|
|
19
38
|
author = [],
|
|
20
39
|
common_name = [],
|
|
21
40
|
code_2000 = [],
|
|
22
41
|
id_eunis = [],
|
|
23
|
-
license = [],
|
|
24
|
-
// number_sites = [],
|
|
25
42
|
picture_url = [],
|
|
26
43
|
scientific_name = [],
|
|
27
44
|
source = [],
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} =
|
|
45
|
+
license = [],
|
|
46
|
+
} = species;
|
|
47
|
+
const { attribution_copyright = [] } = species_pictures;
|
|
31
48
|
|
|
32
|
-
const
|
|
49
|
+
const pictures = useMemo(() => {
|
|
50
|
+
const weburls = species_pictures?.['WebURL'] || [];
|
|
51
|
+
if (weburls.filter((url) => !!url).length > 0) {
|
|
52
|
+
return weburls;
|
|
53
|
+
}
|
|
54
|
+
return picture_url;
|
|
55
|
+
}, [species_pictures, picture_url]);
|
|
56
|
+
|
|
57
|
+
const pictures_length = useMemo(
|
|
58
|
+
() => pictures.filter((picture) => !!picture)?.length,
|
|
59
|
+
[pictures],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (!species_provider && props.mode === 'edit') {
|
|
63
|
+
return 'species banner block, species provider undefined';
|
|
64
|
+
}
|
|
65
|
+
if (!species_pictures_provider && props.mode === 'edit') {
|
|
66
|
+
return 'Species banner block, species pictures provider undefined';
|
|
67
|
+
}
|
|
33
68
|
|
|
34
69
|
if (!id_eunis[0]) return '';
|
|
35
70
|
return (
|
|
@@ -111,9 +146,13 @@ const View = (props) => {
|
|
|
111
146
|
size="32px"
|
|
112
147
|
/>
|
|
113
148
|
</button>
|
|
114
|
-
|
|
115
|
-
{
|
|
116
|
-
|
|
149
|
+
{!!attribution_copyright[activeSlide] ? (
|
|
150
|
+
<p>{attribution_copyright[activeSlide]}</p>
|
|
151
|
+
) : !!source[activeSlide] && !!license[activeSlide] ? (
|
|
152
|
+
<p title={`${source[activeSlide]} - ${license[activeSlide]}`}>
|
|
153
|
+
{source[activeSlide]} - {license[activeSlide]}
|
|
154
|
+
</p>
|
|
155
|
+
) : null}
|
|
117
156
|
</div>
|
|
118
157
|
<SwiperLoader>
|
|
119
158
|
{() => {
|
|
@@ -131,9 +170,12 @@ const View = (props) => {
|
|
|
131
170
|
swiperEl.current = swiper;
|
|
132
171
|
}}
|
|
133
172
|
>
|
|
134
|
-
{
|
|
173
|
+
{pictures.map((source, index) => (
|
|
135
174
|
<SwiperSlide>
|
|
136
|
-
<img
|
|
175
|
+
<img
|
|
176
|
+
src={getSource(source)}
|
|
177
|
+
alt={pictures[index]}
|
|
178
|
+
/>
|
|
137
179
|
</SwiperSlide>
|
|
138
180
|
))}
|
|
139
181
|
</Swiper>
|
|
@@ -151,9 +193,12 @@ const View = (props) => {
|
|
|
151
193
|
previewEl.current[0] = swiper;
|
|
152
194
|
}}
|
|
153
195
|
>
|
|
154
|
-
{
|
|
196
|
+
{pictures.map((source, index) => (
|
|
155
197
|
<SwiperSlide>
|
|
156
|
-
<img
|
|
198
|
+
<img
|
|
199
|
+
src={getSource(source)}
|
|
200
|
+
alt={pictures[index]}
|
|
201
|
+
/>
|
|
157
202
|
</SwiperSlide>
|
|
158
203
|
))}
|
|
159
204
|
</Swiper>
|
|
@@ -172,9 +217,12 @@ const View = (props) => {
|
|
|
172
217
|
previewEl.current[1] = swiper;
|
|
173
218
|
}}
|
|
174
219
|
>
|
|
175
|
-
{
|
|
220
|
+
{pictures.map((source, index) => (
|
|
176
221
|
<SwiperSlide>
|
|
177
|
-
<img
|
|
222
|
+
<img
|
|
223
|
+
src={getSource(source)}
|
|
224
|
+
alt={pictures[index]}
|
|
225
|
+
/>
|
|
178
226
|
</SwiperSlide>
|
|
179
227
|
))}
|
|
180
228
|
</Swiper>
|
|
@@ -193,4 +241,21 @@ const View = (props) => {
|
|
|
193
241
|
);
|
|
194
242
|
};
|
|
195
243
|
|
|
196
|
-
|
|
244
|
+
const View = compose(
|
|
245
|
+
connectToMultipleProviders((props) => ({
|
|
246
|
+
providers: [
|
|
247
|
+
{
|
|
248
|
+
provider_url: props.data?.species_provider,
|
|
249
|
+
},
|
|
250
|
+
{ provider_url: props.data?.species_pictures_provider },
|
|
251
|
+
],
|
|
252
|
+
})),
|
|
253
|
+
)(_View);
|
|
254
|
+
|
|
255
|
+
export default (props) => {
|
|
256
|
+
return (
|
|
257
|
+
<VisibilitySensor Placeholder={() => <div>loading.... </div>}>
|
|
258
|
+
<View {...props} />
|
|
259
|
+
</VisibilitySensor>
|
|
260
|
+
);
|
|
261
|
+
};
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import worldSVG from '@plone/volto/icons/world.svg';
|
|
2
|
+
import Edit from './Edit';
|
|
3
|
+
import View from './View';
|
|
3
4
|
|
|
4
5
|
export default (config) => {
|
|
5
|
-
config.blocks.blocksConfig.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
config.blocks.blocksConfig.species_banner = {
|
|
7
|
+
id: 'species_banner',
|
|
8
|
+
title: 'Species banner',
|
|
9
|
+
icon: worldSVG,
|
|
10
|
+
group: 'natura_2000',
|
|
11
|
+
edit: Edit,
|
|
12
|
+
view: View,
|
|
13
|
+
restricted: false,
|
|
14
|
+
mostUsed: false,
|
|
15
|
+
sidebarTab: 1,
|
|
16
|
+
blocks: {},
|
|
17
|
+
security: {
|
|
18
|
+
addPermission: [],
|
|
19
|
+
view: [],
|
|
14
20
|
},
|
|
21
|
+
blockHasOwnFocusManagement: false,
|
|
15
22
|
};
|
|
16
23
|
return config;
|
|
17
24
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const getSchema = (
|
|
1
|
+
const getSchema = () => {
|
|
2
2
|
return {
|
|
3
3
|
title: 'Species banner',
|
|
4
4
|
|
|
@@ -6,11 +6,32 @@ const getSchema = (props) => {
|
|
|
6
6
|
{
|
|
7
7
|
id: 'default',
|
|
8
8
|
title: 'Default',
|
|
9
|
-
fields: [
|
|
9
|
+
fields: [
|
|
10
|
+
'species_provider',
|
|
11
|
+
'species_pictures_provider',
|
|
12
|
+
'allowedParams',
|
|
13
|
+
],
|
|
10
14
|
},
|
|
11
15
|
],
|
|
12
16
|
|
|
13
|
-
properties: {
|
|
17
|
+
properties: {
|
|
18
|
+
species_provider: {
|
|
19
|
+
title: 'Species provider',
|
|
20
|
+
widget: 'url',
|
|
21
|
+
},
|
|
22
|
+
species_pictures_provider: {
|
|
23
|
+
title: 'Species pictures provider',
|
|
24
|
+
widget: 'url',
|
|
25
|
+
},
|
|
26
|
+
allowedParams: {
|
|
27
|
+
title: 'Allowed params',
|
|
28
|
+
type: 'array',
|
|
29
|
+
creatable: true,
|
|
30
|
+
items: {
|
|
31
|
+
choices: [],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
14
35
|
|
|
15
36
|
required: [],
|
|
16
37
|
};
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import CopyPaste from './CopyPaste';
|
|
2
1
|
import HashLink from './HashLink';
|
|
3
2
|
|
|
4
3
|
export default (config) => {
|
|
5
4
|
config.settings.appExtras = [
|
|
6
5
|
...(config.settings.appExtras || []),
|
|
7
|
-
{
|
|
8
|
-
match: '/**/edit',
|
|
9
|
-
component: CopyPaste,
|
|
10
|
-
},
|
|
11
6
|
{
|
|
12
7
|
match: '',
|
|
13
8
|
component: HashLink,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Icon, BlockChooser } from '@plone/volto/components';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
blockHasValue,
|
|
5
|
+
buildStyleClassNamesFromData,
|
|
6
|
+
} from '@plone/volto/helpers';
|
|
4
7
|
import config from '@plone/volto/registry';
|
|
5
8
|
import { Button } from 'semantic-ui-react';
|
|
6
9
|
import includes from 'lodash/includes';
|
|
@@ -86,6 +89,8 @@ class EditBlockWrapper extends React.Component {
|
|
|
86
89
|
? data.required
|
|
87
90
|
: includes(config.blocks.requiredBlocks, type);
|
|
88
91
|
|
|
92
|
+
const styles = buildStyleClassNamesFromData(data.styles);
|
|
93
|
+
|
|
89
94
|
// Get editing instructions from block settings or props
|
|
90
95
|
let instructions = data?.instructions?.data || data?.instructions;
|
|
91
96
|
if (!instructions || instructions === '<p><br/></p>') {
|
|
@@ -97,7 +102,9 @@ class EditBlockWrapper extends React.Component {
|
|
|
97
102
|
<div
|
|
98
103
|
ref={draginfo?.innerRef}
|
|
99
104
|
{...(selected ? draginfo?.draggableProps : null)}
|
|
100
|
-
className={`block-editor-${data['@type']}
|
|
105
|
+
className={cx(`block-editor-${data['@type']}`, styles, {
|
|
106
|
+
[data.align]: data.align,
|
|
107
|
+
})}
|
|
101
108
|
>
|
|
102
109
|
{(!selected || !visible || disabled) && (
|
|
103
110
|
<div
|
package/src/helpers.js
CHANGED
|
@@ -146,3 +146,8 @@ export const pathExists = (path, items) => {
|
|
|
146
146
|
}
|
|
147
147
|
return ok;
|
|
148
148
|
};
|
|
149
|
+
|
|
150
|
+
export const replaceQueryParam = (url, param, newValue) => {
|
|
151
|
+
const regex = new RegExp(`(${encodeURIComponent(param)}=)([^&]+)`, 'i');
|
|
152
|
+
return url.replace(regex, `$1${encodeURIComponent(newValue)}`);
|
|
153
|
+
};
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { connect } from 'react-redux';
|
|
3
|
-
import { compose } from 'redux';
|
|
4
|
-
import { Portal } from 'react-portal';
|
|
5
|
-
import { toast } from 'react-toastify';
|
|
6
|
-
import { Button } from 'semantic-ui-react';
|
|
7
|
-
import { getBaseUrl } from '@plone/volto/helpers';
|
|
8
|
-
import { updateContent } from '@plone/volto/actions';
|
|
9
|
-
import { Icon, Toast } from '@plone/volto/components';
|
|
10
|
-
import copySVG from '@plone/volto/icons/copy.svg';
|
|
11
|
-
import pasteSVG from '@plone/volto/icons/paste.svg';
|
|
12
|
-
|
|
13
|
-
import './style.less';
|
|
14
|
-
|
|
15
|
-
const TIMEOUT = 2000;
|
|
16
|
-
|
|
17
|
-
const CopyPaste = (props) => {
|
|
18
|
-
const [readyToRender, setReadyToRender] = React.useState(false);
|
|
19
|
-
const clock = React.useRef(null);
|
|
20
|
-
const time = React.useRef(0);
|
|
21
|
-
const toolbar = React.useRef(null);
|
|
22
|
-
const { content } = props;
|
|
23
|
-
|
|
24
|
-
const copyData = () => {
|
|
25
|
-
navigator.clipboard.writeText(
|
|
26
|
-
JSON.stringify({
|
|
27
|
-
blocks: content.blocks,
|
|
28
|
-
blocks_layout: content.blocks_layout,
|
|
29
|
-
}),
|
|
30
|
-
);
|
|
31
|
-
toast.success(
|
|
32
|
-
<Toast success title={'Success'} content={`Copied blocks`} />,
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const pasteData = () => {
|
|
37
|
-
const message = [
|
|
38
|
-
'============= BRAKING CHANGE =============',
|
|
39
|
-
'\nAre you sure you want to paste from clipboard?',
|
|
40
|
-
'\nThis action will replace all the blocks with those from clipboard and will trigger SUBMIT !!!',
|
|
41
|
-
];
|
|
42
|
-
navigator.clipboard.readText().then((text) => {
|
|
43
|
-
if (
|
|
44
|
-
// eslint-disable-next-line no-alert
|
|
45
|
-
window.confirm(message.join(''))
|
|
46
|
-
) {
|
|
47
|
-
try {
|
|
48
|
-
const data = JSON.parse(text) || {};
|
|
49
|
-
const { blocks = {}, blocks_layout = {} } = data;
|
|
50
|
-
const blocksIds = Object.keys(blocks);
|
|
51
|
-
let valid = true;
|
|
52
|
-
if (
|
|
53
|
-
blocks_layout &&
|
|
54
|
-
blocks_layout.items &&
|
|
55
|
-
blocks_layout.items.length === blocksIds.length
|
|
56
|
-
) {
|
|
57
|
-
blocks_layout.items.forEach((block) => {
|
|
58
|
-
if (valid && !blocksIds.includes(block)) {
|
|
59
|
-
valid = false;
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
if (valid) {
|
|
64
|
-
props.updateContent(getBaseUrl(props.pathname), data);
|
|
65
|
-
toast.success(
|
|
66
|
-
<Toast
|
|
67
|
-
success
|
|
68
|
-
title={'Success'}
|
|
69
|
-
content={'Blocks replaced successfully'}
|
|
70
|
-
/>,
|
|
71
|
-
);
|
|
72
|
-
} else {
|
|
73
|
-
toast.error(
|
|
74
|
-
<Toast
|
|
75
|
-
error
|
|
76
|
-
title={'Error'}
|
|
77
|
-
content={'Your clipboard contains incompatible data'}
|
|
78
|
-
/>,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
toast.error(
|
|
83
|
-
<Toast
|
|
84
|
-
error
|
|
85
|
-
title={'Error'}
|
|
86
|
-
content={'Your clipboard contains incompatible data'}
|
|
87
|
-
/>,
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
React.useEffect(() => {
|
|
95
|
-
clock.current = setInterval(() => {
|
|
96
|
-
const element = document.querySelector('#toolbar .toolbar-actions');
|
|
97
|
-
if (element) {
|
|
98
|
-
setReadyToRender(true);
|
|
99
|
-
clearInterval(clock.current);
|
|
100
|
-
time.current = 0;
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (time.current >= TIMEOUT) {
|
|
104
|
-
clearInterval(clock.current);
|
|
105
|
-
time.current = 0;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
time.current += 100;
|
|
109
|
-
}, 100);
|
|
110
|
-
return () => {
|
|
111
|
-
clearInterval(clock.current);
|
|
112
|
-
time.current = 0;
|
|
113
|
-
};
|
|
114
|
-
}, []);
|
|
115
|
-
|
|
116
|
-
if (!__CLIENT__ || !readyToRender) return '';
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<Portal node={document.querySelector('#toolbar .toolbar-actions')}>
|
|
120
|
-
<div
|
|
121
|
-
ref={toolbar}
|
|
122
|
-
id="__developer_tools"
|
|
123
|
-
onMouseEnter={(e) => {
|
|
124
|
-
if (
|
|
125
|
-
e.altKey &&
|
|
126
|
-
e.shiftKey &&
|
|
127
|
-
toolbar.current &&
|
|
128
|
-
!toolbar.current.classList.contains('__dev_on')
|
|
129
|
-
) {
|
|
130
|
-
toolbar.current.classList.add('__dev_on');
|
|
131
|
-
}
|
|
132
|
-
}}
|
|
133
|
-
onMouseLeave={(e) => {
|
|
134
|
-
if (
|
|
135
|
-
toolbar.current &&
|
|
136
|
-
toolbar.current.classList.contains('__dev_on')
|
|
137
|
-
) {
|
|
138
|
-
toolbar.current.classList.remove('__dev_on');
|
|
139
|
-
}
|
|
140
|
-
}}
|
|
141
|
-
onFocus={() => {}}
|
|
142
|
-
>
|
|
143
|
-
<Button
|
|
144
|
-
className="copy"
|
|
145
|
-
aria-label="Copy blocks data"
|
|
146
|
-
title="Copy blocks data"
|
|
147
|
-
onClick={copyData}
|
|
148
|
-
>
|
|
149
|
-
<Icon name={copySVG} className="circled" size="30px" />
|
|
150
|
-
</Button>
|
|
151
|
-
<Button
|
|
152
|
-
className="paste"
|
|
153
|
-
aria-label="Paste blocks data"
|
|
154
|
-
title="Paste blocks data"
|
|
155
|
-
onClick={pasteData}
|
|
156
|
-
disabled={props.updateRequest.loading}
|
|
157
|
-
>
|
|
158
|
-
<Icon name={pasteSVG} className="circled" size="30px" />
|
|
159
|
-
</Button>
|
|
160
|
-
</div>
|
|
161
|
-
</Portal>
|
|
162
|
-
);
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
export default compose(
|
|
166
|
-
connect(
|
|
167
|
-
(state, props) => ({
|
|
168
|
-
content: state.content.data,
|
|
169
|
-
updateRequest: state.content.update,
|
|
170
|
-
pathname: props.location.pathname,
|
|
171
|
-
}),
|
|
172
|
-
{
|
|
173
|
-
updateContent,
|
|
174
|
-
},
|
|
175
|
-
),
|
|
176
|
-
)(CopyPaste);
|