@eeacms/volto-embed 7.0.2 → 9.0.0

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.
@@ -1,67 +1,53 @@
1
1
  import { defineMessages } from 'react-intl';
2
2
 
3
3
  const messages = defineMessages({
4
- Maps: {
5
- id: 'Maps',
6
- defaultMessage: 'Maps',
7
- },
8
- AltText: {
9
- id: 'Alt text',
10
- defaultMessage: 'Alt text',
11
- },
12
- MapsURL: {
13
- id: 'Maps URL',
14
- defaultMessage: 'Maps URL',
15
- },
16
- Alignment: {
17
- id: 'Alignment',
18
- defaultMessage: 'Alignment',
19
- },
20
4
  CSSHeight: {
21
- id: 'CSSHeight',
5
+ id: 'CSS height',
22
6
  defineMessages: 'CSS height',
23
7
  },
24
8
  CSSHeightDescription: {
25
- id: 'CSSHeightDescription',
9
+ id: 'Iframe height',
26
10
  defineMessages: 'Iframe height',
27
11
  },
28
12
  });
29
13
 
30
- export const MapsSchema = (props) => ({
31
- title: props.intl.formatMessage(messages.Maps),
32
- block: 'Maps',
33
- fieldsets: [
34
- {
35
- id: 'default',
36
- title: 'Default',
37
- fields: ['url', 'title', 'align', 'height'],
38
- },
39
- ],
14
+ export const MapsSchema = (props) => {
15
+ return {
16
+ title: props.intl.messages['Maps'],
17
+ block: 'Maps',
18
+ fieldsets: [
19
+ {
20
+ id: 'default',
21
+ title: 'Default',
22
+ fields: ['url', 'title', 'align', 'height'],
23
+ },
24
+ ],
40
25
 
41
- properties: {
42
- url: {
43
- title: props.intl.formatMessage(messages.MapsURL),
44
- widget: 'url',
26
+ properties: {
27
+ url: {
28
+ title: props.intl.messages['Maps URL'],
29
+ widget: 'url',
30
+ },
31
+ title: {
32
+ title: props.intl.messages['Alt text'],
33
+ },
34
+ align: {
35
+ title: props.intl.messages['Alignment'],
36
+ widget: 'align',
37
+ },
38
+ height: {
39
+ title: (
40
+ <a
41
+ rel="noreferrer"
42
+ target="_blank"
43
+ href="https://developer.mozilla.org/en-US/docs/Web/CSS/height"
44
+ >
45
+ {props.intl.formatMessage(messages.CSSHeight)}
46
+ </a>
47
+ ),
48
+ description: props.intl.formatMessage(messages.CSSHeightDescription),
49
+ },
45
50
  },
46
- title: {
47
- title: props.intl.formatMessage(messages.AltText),
48
- },
49
- align: {
50
- title: props.intl.formatMessage(messages.Alignment),
51
- widget: 'align',
52
- },
53
- height: {
54
- title: (
55
- <a
56
- rel="noreferrer"
57
- target="_blank"
58
- href="https://developer.mozilla.org/en-US/docs/Web/CSS/height"
59
- >
60
- {props.intl.formatMessage(messages.CSSHeight)}
61
- </a>
62
- ),
63
- description: props.intl.formatMessage(messages.CSSHeightDescription),
64
- },
65
- },
66
- required: [],
67
- });
51
+ required: [],
52
+ };
53
+ };
@@ -1,5 +1,9 @@
1
1
  import installMaps from './Maps';
2
+ import installEmbedMaps from './EmbedMaps';
2
3
 
3
4
  export default function installBlocks(config) {
4
- return [installMaps].reduce((acc, apply) => apply(acc), config);
5
+ return [installMaps, installEmbedMaps].reduce(
6
+ (acc, apply) => apply(acc),
7
+ config,
8
+ );
5
9
  }
@@ -0,0 +1,106 @@
1
+ import React, { useEffect, useState, useRef } from 'react';
2
+ import { compose } from 'redux';
3
+ import { connect } from 'react-redux';
4
+ import { defineMessages, injectIntl } from 'react-intl';
5
+ import cx from 'classnames';
6
+ import {
7
+ FigureNote,
8
+ Sources,
9
+ MoreInfo,
10
+ Share,
11
+ Enlarge,
12
+ } from '@eeacms/volto-embed/Toolbar';
13
+ import PrivacyProtection from '@eeacms/volto-embed/PrivacyProtection/PrivacyProtection';
14
+
15
+ import './style.less';
16
+
17
+ const messages = defineMessages({
18
+ EmbededGoogleMaps: {
19
+ id: 'Embeded Google Maps',
20
+ defaultMessage: 'Embeded Google Maps',
21
+ },
22
+ });
23
+
24
+ function EmbedMap({ data, intl, id, screen }) {
25
+ const el = useRef();
26
+ const [mobile, setMobile] = useState(false);
27
+
28
+ useEffect(() => {
29
+ if (el.current) {
30
+ const visWidth = el.current.offsetWidth;
31
+
32
+ if (visWidth < 600 && !mobile) {
33
+ setMobile(true);
34
+ } else if (visWidth >= 600 && mobile) {
35
+ setMobile(false);
36
+ }
37
+ }
38
+ }, [screen, mobile]);
39
+
40
+ return (
41
+ <div
42
+ ref={el}
43
+ className={cx(
44
+ 'block maps align',
45
+ {
46
+ center: !Boolean(data.align),
47
+ },
48
+ data.align,
49
+ )}
50
+ >
51
+ <div
52
+ className={cx('maps-inner', {
53
+ 'full-width': data.align === 'full',
54
+ })}
55
+ >
56
+ <PrivacyProtection
57
+ data={data}
58
+ id={id}
59
+ height={data.height}
60
+ useVisibilitySensor={data.useVisibilitySensor ?? true}
61
+ >
62
+ <iframe
63
+ title={intl.formatMessage(messages.EmbededGoogleMaps)}
64
+ src={data.url}
65
+ className="google-map"
66
+ frameBorder="0"
67
+ allowFullScreen
68
+ style={data.height ? { height: data.height } : {}}
69
+ />
70
+ </PrivacyProtection>
71
+ </div>
72
+ <div className={cx('visualization-toolbar', { mobile })}>
73
+ <div className="left-col">
74
+ {data.with_notes && <FigureNote notes={data.figure_note || []} />}
75
+ {data.with_sources && <Sources sources={data.sources} />}
76
+ {data.with_more_info && <MoreInfo href={data['@id']} />}
77
+ </div>
78
+ <div className="right-col">
79
+ {data.with_enlarge && (
80
+ <Enlarge className="enlarge-embed-maps">
81
+ <EmbedMap
82
+ data={{
83
+ ...data,
84
+ height: '100%',
85
+ with_notes: false,
86
+ with_sources: false,
87
+ with_more_info: false,
88
+ with_share: false,
89
+ with_enlarge: false,
90
+ }}
91
+ id={id}
92
+ intl={intl}
93
+ />
94
+ </Enlarge>
95
+ )}
96
+ {data.with_share && <Share href={data['@id']} />}
97
+ </div>
98
+ </div>
99
+ </div>
100
+ );
101
+ }
102
+
103
+ export default compose(
104
+ injectIntl,
105
+ connect((state) => ({ screen: state.screen })),
106
+ )(EmbedMap);
@@ -0,0 +1,66 @@
1
+ import React from 'react';
2
+ import renderer from 'react-test-renderer';
3
+ import configureStore from 'redux-mock-store';
4
+ import { Provider } from 'react-intl-redux';
5
+ import config from '@plone/volto/registry';
6
+
7
+ import EmbedMap from './EmbedMap';
8
+
9
+ const mockStore = configureStore();
10
+
11
+ jest.mock('@plone/volto/components', () => ({
12
+ __esModule: true,
13
+ UniversalLink: ({ children, href }) => {
14
+ return <a href={href}>{children}</a>;
15
+ },
16
+ }));
17
+
18
+ config.blocks.blocksConfig = {
19
+ ...config.blocks.blocksConfig,
20
+ maps: {
21
+ id: 'maps',
22
+ title: 'Maps',
23
+ group: 'media',
24
+ extensions: {},
25
+ variations: [],
26
+ restricted: false,
27
+ mostUsed: true,
28
+ sidebarTab: 1,
29
+ security: {
30
+ addPermission: [],
31
+ view: [],
32
+ },
33
+ },
34
+ };
35
+
36
+ test('renders map component', () => {
37
+ const store = mockStore({
38
+ intl: {
39
+ locale: 'en',
40
+ messages: {},
41
+ },
42
+ content: {
43
+ create: {},
44
+ },
45
+ connected_data_parameters: {},
46
+ });
47
+ const component = renderer.create(
48
+ <Provider store={store}>
49
+ <EmbedMap
50
+ id="my-map"
51
+ data={{
52
+ with_notes: false,
53
+ with_sources: false,
54
+ with_more_info: true,
55
+ with_share: true,
56
+ with_enlarge: true,
57
+ url:
58
+ 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3027.7835278268726!2d14.38842915203974!3d40.634655679238854!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x133b994881d943cb%3A0x6ab93db57d3272f0!2sHotel+Mediterraneo+Sorrento!5e0!3m2!1sen!2ses!4v1550168740166',
59
+ useVisibilitySensor: false,
60
+ }}
61
+ />
62
+ </Provider>,
63
+ );
64
+ const json = component.toJSON();
65
+ expect(json).toMatchSnapshot();
66
+ });
@@ -0,0 +1,16 @@
1
+ .embed-map .block.maps .maps-inner {
2
+ margin-bottom: 1rem;
3
+ }
4
+
5
+ .ui.modal.enlarge-modal.enlarge-embed-maps {
6
+ .content,
7
+ .content .block.maps,
8
+ .content .block.maps .maps-inner {
9
+ height: 100%;
10
+ }
11
+
12
+ .close.icon {
13
+ top: 2rem;
14
+ right: 2rem;
15
+ }
16
+ }
@@ -1,8 +1,8 @@
1
1
  import React, { useState } from 'react';
2
2
  import cx from 'classnames';
3
+ import { isNumber } from 'lodash';
3
4
  import { compose } from 'redux';
4
5
  import { useSelector, useDispatch } from 'react-redux';
5
- import VisibilitySensor from 'react-visibility-sensor';
6
6
  import {
7
7
  Placeholder,
8
8
  Dimmer,
@@ -21,6 +21,7 @@ import {
21
21
  getConnectedDataParametersForContext,
22
22
  getFilteredURL,
23
23
  } from '@eeacms/volto-datablocks/helpers';
24
+ import { VisibilitySensor } from '@eeacms/volto-datablocks/components';
24
25
 
25
26
  import { createImageUrl } from './helpers';
26
27
  import { ProtectionSchema } from './schema';
@@ -86,7 +87,7 @@ const PrivacyProtection = (props) => {
86
87
  intl,
87
88
  path,
88
89
  cookies,
89
- height = '',
90
+ useVisibilitySensor = true,
90
91
  } = props;
91
92
  const {
92
93
  enabled = false,
@@ -96,7 +97,6 @@ const PrivacyProtection = (props) => {
96
97
  } = data.dataprotection || {};
97
98
 
98
99
  const [image, setImage] = React.useState(null);
99
- const [visible, setVisibility] = useState(false);
100
100
  const defaultShow = canShow(privacy_cookie_key, cookies);
101
101
  const [show, setShow] = useState(defaultShow);
102
102
  const [remember, setRemember] = useState(
@@ -112,6 +112,12 @@ const PrivacyProtection = (props) => {
112
112
  });
113
113
  const url = getFilteredURL(data.url, connected_data_parameters);
114
114
 
115
+ const height = React.useMemo(() => {
116
+ if (!props.height || enabled || !show) return 'auto';
117
+ if (isNumber(props.height)) return `${props.height}px`;
118
+ return props.height;
119
+ }, [props.height, enabled, show]);
120
+
115
121
  React.useEffect(() => {
116
122
  if (bgImg) {
117
123
  setImage(createImageUrl(bgImg)); //create imageUrl from uploaded image
@@ -160,96 +166,92 @@ const PrivacyProtection = (props) => {
160
166
  });
161
167
  }
162
168
  }, [enabled, url, path, dispatch, bgImg, show, intl, editable]);
169
+
163
170
  return (
164
171
  <VisibilitySensor
165
- onChange={(isVisible) => {
166
- !visible && isVisible && setVisibility(true);
167
- }}
168
- partialVisibility={true}
169
- offset={{ bottom: 200 }}
172
+ Placeholder={() => (
173
+ <Placeholder style={{ height: '100%', width: '100%' }}>
174
+ <Placeholder.Image rectangular />
175
+ </Placeholder>
176
+ )}
177
+ useVisibilitySensor={useVisibilitySensor}
170
178
  >
171
- {visible ? (
172
- <div
173
- className={cx('privacy-protection-wrapper', className)}
174
- style={{
175
- position: 'relative',
176
- height: height && (!enabled || !show) ? `${height}px` : 'auto',
177
- minHeight: '200px',
178
- overflow: 'hidden',
179
- }}
180
- >
181
- {!enabled || show ? (
182
- children
183
- ) : !__DEVELOPMENT__ && !image ? (
184
- <Dimmer active>
185
- <Loader />
186
- </Dimmer>
187
- ) : (
188
- <div
189
- className="privacy-protection"
190
- style={
191
- image
192
- ? {
193
- backgroundImage: `url(${image})`,
194
- backgroundRepeat: 'no-repeat',
195
- backgroundSize: 'cover',
196
- backgroundPosition: 'center -70px',
197
- }
198
- : {}
199
- }
200
- >
201
- <div className="overlay">
202
- <div className="wrapped">
203
- <div className="privacy-button">
204
- <Button
205
- primary
206
- onClick={() => {
207
- setShow(true);
208
- if (remember) {
209
- saveCookie(privacy_cookie_key, cookies);
210
- }
179
+ <div
180
+ className={cx('privacy-protection-wrapper', className)}
181
+ style={{
182
+ position: 'relative',
183
+ overflow: 'hidden',
184
+ height,
185
+ minHeight: height !== 'auto' ? height : '200px',
186
+ }}
187
+ >
188
+ {!enabled || show ? (
189
+ children
190
+ ) : !__DEVELOPMENT__ && !image ? (
191
+ <Dimmer active>
192
+ <Loader />
193
+ </Dimmer>
194
+ ) : (
195
+ <div
196
+ className="privacy-protection"
197
+ style={
198
+ image
199
+ ? {
200
+ backgroundImage: `url(${image})`,
201
+ backgroundRepeat: 'no-repeat',
202
+ backgroundSize: 'cover',
203
+ backgroundPosition: 'center -70px',
204
+ }
205
+ : {}
206
+ }
207
+ >
208
+ <div className="overlay">
209
+ <div className="wrapped">
210
+ <div className="privacy-button">
211
+ <Button
212
+ primary
213
+ onClick={() => {
214
+ setShow(true);
215
+ if (remember) {
216
+ saveCookie(privacy_cookie_key, cookies);
217
+ }
218
+ }}
219
+ >
220
+ Show external content
221
+ </Button>
222
+ </div>
223
+
224
+ {!editable && (
225
+ <div className="privacy-toggle">
226
+ <Checkbox
227
+ toggle
228
+ label="Remember my choice"
229
+ id={`remember-choice-${id}`}
230
+ onChange={(ev, { checked }) => {
231
+ setRemember(checked);
211
232
  }}
212
- >
213
- Show external content
214
- </Button>
233
+ checked={remember}
234
+ />
215
235
  </div>
236
+ )}
216
237
 
217
- {!editable && (
218
- <div className="privacy-toggle">
219
- <Checkbox
220
- toggle
221
- label="Remember my choice"
222
- id={`remember-choice-${id}`}
223
- onChange={(ev, { checked }) => {
224
- setRemember(checked);
225
- }}
226
- checked={remember}
227
- />
228
- </div>
238
+ <p className="discreet">
239
+ Your choice will be saved in a cookie managed by{' '}
240
+ {config.settings.ownDomain || '.eea.europa.eu'} that will
241
+ expire in {getExpDays()} days.
242
+ </p>
243
+ <p className="discreet">
244
+ {serializeNodes(
245
+ privacy_statement ||
246
+ ProtectionSchema().properties.privacy_statement
247
+ .defaultValue,
229
248
  )}
230
-
231
- <p className="discreet">
232
- Your choice will be saved in a cookie managed by{' '}
233
- {config.settings.ownDomain || '.eea.europa.eu'} that will
234
- expire in {getExpDays()} days.
235
- </p>
236
- <p className="discreet">
237
- {serializeNodes(
238
- privacy_statement ||
239
- ProtectionSchema().properties.privacy_statement
240
- .defaultValue,
241
- )}
242
- </p>
243
- </div>
249
+ </p>
244
250
  </div>
245
251
  </div>
246
- )}
247
- </div>
248
- ) : (
249
- <Placeholder style={{ height: '100%', width: '100%' }}>
250
- <Placeholder.Image rectangular />
251
- </Placeholder>
252
- )}
252
+ </div>
253
+ )}
254
+ </div>
253
255
  </VisibilitySensor>
254
256
  );
255
257
  };
@@ -1,29 +1,42 @@
1
1
  import React, { useState } from 'react';
2
+ import { isFunction } from 'lodash';
2
3
  import { Modal } from 'semantic-ui-react';
4
+ import cx from 'classnames';
3
5
 
4
- const EnlargeWidget = ({ children }) => {
6
+ const Enlarge = ({ children, className, onClick }) => {
5
7
  const [isOpen, setIsOpen] = useState(false);
6
8
 
7
9
  return (
8
10
  <div className="enlarge">
9
- <button className="trigger-button" onClick={() => setIsOpen(true)}>
11
+ <button
12
+ className="trigger-button"
13
+ onClick={() => {
14
+ if (isFunction(onClick)) {
15
+ onClick({ setOpen: setIsOpen });
16
+ } else {
17
+ setIsOpen(true);
18
+ }
19
+ }}
20
+ >
10
21
  <i className="ri-fullscreen-line" />
11
22
  Enlarge
12
23
  </button>
13
- <Modal
14
- open={isOpen}
15
- closeIcon={
16
- <span className="close icon">
17
- <i class="ri-close-line" />
18
- </span>
19
- }
20
- onClose={() => setIsOpen(false)}
21
- className="enlarge-modal"
22
- >
23
- <Modal.Content>{children}</Modal.Content>
24
- </Modal>
24
+ {children && (
25
+ <Modal
26
+ open={isOpen}
27
+ closeIcon={
28
+ <span className="close icon">
29
+ <i class="ri-close-line" />
30
+ </span>
31
+ }
32
+ onClose={() => setIsOpen(false)}
33
+ className={cx('enlarge-modal', className)}
34
+ >
35
+ <Modal.Content>{children}</Modal.Content>
36
+ </Modal>
37
+ )}
25
38
  </div>
26
39
  );
27
40
  };
28
41
 
29
- export default EnlargeWidget;
42
+ export default Enlarge;
@@ -38,8 +38,7 @@ export default function FigureNote({ notes = [] }) {
38
38
  <button className={cx('trigger-button', { open })}>Note</button>
39
39
  </div>
40
40
  }
41
- >
42
- <Popup.Content>{serializeText(notes)}</Popup.Content>
43
- </Popup>
41
+ content={serializeText(notes)}
42
+ />
44
43
  );
45
44
  }