@eeacms/volto-n2k 1.0.25 → 1.0.27

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 CHANGED
@@ -4,6 +4,13 @@ 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.27](https://github.com/eea/volto-n2k/compare/1.0.26...1.0.27) - 6 June 2023
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - fix navigation-anchors [Miu Razvan - [`5b140f7`](https://github.com/eea/volto-n2k/commit/5b140f7eea69aa5f310d62c25147a8c153415d2c)]
12
+ ### [1.0.26](https://github.com/eea/volto-n2k/compare/1.0.25...1.0.26) - 29 May 2023
13
+
7
14
  ### [1.0.25](https://github.com/eea/volto-n2k/compare/1.0.24...1.0.25) - 26 April 2023
8
15
 
9
16
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-n2k",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "volto-n2k: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -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
- return slider.current && tabsList.length > 1 ? (
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.current) {
34
- slider.current.slickGoTo(index);
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.current ? (
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.current) {
65
- slider.current.slickPrev();
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.current) {
81
- slider.current.slickNext();
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
- // const [imgHeight, setImgHeight] = React.useState(0);
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
- // const panes = tabsList.map((tab, index) => {
138
- // return {
139
- // id: tab,
140
- // renderItem: (
141
- // <React.Fragment key={`slide-${tab}`}>
142
- // <RenderBlocks {...props} metadata={metadata} content={tabs[tab]} />
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.current.slickGoTo(index);
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={slider}
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
- const EditWrapper = compose(
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 { providers = [] } = props.data;
20
- const habitat = props.providers_data?.[providers[0]?.provider_url] || {};
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?.[providers[1]?.provider_url] || {};
34
+ props.providers_data?.[habitat_pictures_provider] || {};
23
35
 
24
- const {
25
- code_2000,
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 = pictures?.length;
35
- // const picture_names = habitat_pictures?.['filename'] || [];
36
- // const copyright = habitat_pictures?.['attribution_copyright'] || [];
40
+ const pictures_length = useMemo(
41
+ () => pictures.filter((picture) => !!picture)?.length,
42
+ [pictures],
43
+ );
37
44
 
38
- if (!code_2000 && props.mode === 'edit') {
39
- return 'Habitat banner block (code_2000 undefined)';
45
+ if (!habitat_provider && props.mode === 'edit') {
46
+ return 'Habitat banner block, habitat provider undefined';
40
47
  }
41
- if (!code_2000) return null;
42
- if (activeSlide) {
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&nbsp;&nbsp;&nbsp;{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
- {/* <p title={`${source[activeSlide]} - ${license[activeSlide]}`}>
112
- {source[activeSlide]} - {license[activeSlide]}
113
- </p> */}
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 src={source} alt={pictures[index]} />
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 src={source} alt={pictures[index]} />
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 src={source} alt={pictures[index]} />
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: props.data?.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 SourceSchema = {
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: ['providers', 'allowedParams'],
45
- },
46
- {
47
- id: 'sources',
48
- title: 'Sources',
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
- providers: {
55
- title: 'Data provider',
56
- schema: DataProvidersSchema,
57
- widget: 'object_list',
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'],
@@ -38,7 +38,10 @@ const View = (props) => {
38
38
 
39
39
  const onScroll = () => {
40
40
  const top = document.documentElement.scrollTop;
41
- const offsetHeight = anchorsRef.current?.offsetHeight + 16;
41
+ const offsetHeight =
42
+ screen.page?.width > 767
43
+ ? anchorsRef.current?.offsetHeight + 32
44
+ : document.querySelector('.eea.header .ui.sticky')?.offsetHeight + 32;
42
45
  let newActiveHash,
43
46
  maxTop = 0;
44
47
  hashList.forEach((hash) => {
@@ -53,8 +56,7 @@ const View = (props) => {
53
56
  }
54
57
  setOffsetHeight(offsetHeight);
55
58
  setHeight(
56
- document.querySelector('.eea.header .fixed-container > .ui.sticky')
57
- ?.offsetHeight,
59
+ document.querySelector('.eea.header .ui.sticky')?.offsetHeight + 16,
58
60
  );
59
61
  };
60
62
 
@@ -74,11 +76,16 @@ const View = (props) => {
74
76
  className={cx('sticky-navigation-anchors', {
75
77
  'full-width': sticky,
76
78
  'is-sticky': sticky,
79
+ 'sticky-broken': screen.page?.width <= 767,
77
80
  })}
78
81
  >
79
82
  <div
80
83
  className={cx('navigation-anchors', data.className)}
81
- style={{ ...(height ? { height: `${height}px` } : {}) }}
84
+ style={{
85
+ ...(height && screen.page?.width > 767
86
+ ? { height: `${height}px` }
87
+ : {}),
88
+ }}
82
89
  ref={anchorsRef}
83
90
  >
84
91
  <Container>
@@ -15,8 +15,6 @@ div#view .ui.container > .sticky-navigation-anchors {
15
15
  }
16
16
 
17
17
  .sticky-navigation-anchors {
18
- z-index: 5 !important;
19
-
20
18
  &.is-sticky {
21
19
  .navigation-anchors {
22
20
  background-color: #fff !important;
@@ -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 View = (props) => {
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 provider_data = props.provider_data || {};
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
- // source_url = [],
29
- // species_group_name = [],
30
- } = provider_data;
45
+ license = [],
46
+ } = species;
47
+ const { attribution_copyright = [] } = species_pictures;
31
48
 
32
- const pictures_length = picture_url?.length;
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
- <p title={`${source[activeSlide]} - ${license[activeSlide]}`}>
115
- {source[activeSlide]} - {license[activeSlide]}
116
- </p>
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
- {picture_url.map((source, index) => (
173
+ {pictures.map((source, index) => (
135
174
  <SwiperSlide>
136
- <img src={source} alt={picture_url[index]} />
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
- {picture_url.map((source, index) => (
196
+ {pictures.map((source, index) => (
155
197
  <SwiperSlide>
156
- <img src={source} alt={picture_url[index]} />
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
- {picture_url.map((source, index) => (
220
+ {pictures.map((source, index) => (
176
221
  <SwiperSlide>
177
- <img src={source} alt={picture_url[index]} />
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
- export default View;
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....&nbsp;</div>}>
258
+ <View {...props} />
259
+ </VisibilitySensor>
260
+ );
261
+ };
@@ -1,17 +1,24 @@
1
- import SpeciesBannerView from './View';
2
- import getSchema from './schema';
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.custom_connected_block = {
6
- ...config.blocks.blocksConfig.custom_connected_block,
7
- blocks: {
8
- ...config.blocks.blocksConfig.custom_connected_block.blocks,
9
- species_banner: {
10
- view: SpeciesBannerView,
11
- getSchema: getSchema,
12
- title: 'Species banner',
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 = (props) => {
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,
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);
@@ -1,3 +0,0 @@
1
- import CopyPaste from './CopyPaste';
2
-
3
- export default CopyPaste;
@@ -1,18 +0,0 @@
1
- #__developer_tools {
2
- .ui.button {
3
- color: #007eb1;
4
- opacity: 0;
5
- pointer-events: none;
6
-
7
- &.disabled {
8
- opacity: 0 !important;
9
- }
10
- }
11
-
12
- &.__dev_on {
13
- .ui.button {
14
- opacity: 1;
15
- pointer-events: all;
16
- }
17
- }
18
- }