@eeacms/volto-embed 7.0.2 → 8.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,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,6 @@ const PrivacyProtection = (props) => {
86
87
  intl,
87
88
  path,
88
89
  cookies,
89
- height = '',
90
90
  } = props;
91
91
  const {
92
92
  enabled = false,
@@ -96,7 +96,6 @@ const PrivacyProtection = (props) => {
96
96
  } = data.dataprotection || {};
97
97
 
98
98
  const [image, setImage] = React.useState(null);
99
- const [visible, setVisibility] = useState(false);
100
99
  const defaultShow = canShow(privacy_cookie_key, cookies);
101
100
  const [show, setShow] = useState(defaultShow);
102
101
  const [remember, setRemember] = useState(
@@ -112,6 +111,12 @@ const PrivacyProtection = (props) => {
112
111
  });
113
112
  const url = getFilteredURL(data.url, connected_data_parameters);
114
113
 
114
+ const height = React.useMemo(() => {
115
+ if (!props.height || enabled || !show) return 'auto';
116
+ if (isNumber(props.height)) return `${props.height}px`;
117
+ return props.height;
118
+ }, [props.height, enabled, show]);
119
+
115
120
  React.useEffect(() => {
116
121
  if (bgImg) {
117
122
  setImage(createImageUrl(bgImg)); //create imageUrl from uploaded image
@@ -160,96 +165,91 @@ const PrivacyProtection = (props) => {
160
165
  });
161
166
  }
162
167
  }, [enabled, url, path, dispatch, bgImg, show, intl, editable]);
168
+
163
169
  return (
164
170
  <VisibilitySensor
165
- onChange={(isVisible) => {
166
- !visible && isVisible && setVisibility(true);
167
- }}
168
- partialVisibility={true}
169
- offset={{ bottom: 200 }}
171
+ Placeholder={() => (
172
+ <Placeholder style={{ height: '100%', width: '100%' }}>
173
+ <Placeholder.Image rectangular />
174
+ </Placeholder>
175
+ )}
170
176
  >
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
- }
177
+ <div
178
+ className={cx('privacy-protection-wrapper', className)}
179
+ style={{
180
+ position: 'relative',
181
+ overflow: 'hidden',
182
+ height,
183
+ minHeight: height !== 'auto' ? height : '200px',
184
+ }}
185
+ >
186
+ {!enabled || show ? (
187
+ children
188
+ ) : !__DEVELOPMENT__ && !image ? (
189
+ <Dimmer active>
190
+ <Loader />
191
+ </Dimmer>
192
+ ) : (
193
+ <div
194
+ className="privacy-protection"
195
+ style={
196
+ image
197
+ ? {
198
+ backgroundImage: `url(${image})`,
199
+ backgroundRepeat: 'no-repeat',
200
+ backgroundSize: 'cover',
201
+ backgroundPosition: 'center -70px',
202
+ }
203
+ : {}
204
+ }
205
+ >
206
+ <div className="overlay">
207
+ <div className="wrapped">
208
+ <div className="privacy-button">
209
+ <Button
210
+ primary
211
+ onClick={() => {
212
+ setShow(true);
213
+ if (remember) {
214
+ saveCookie(privacy_cookie_key, cookies);
215
+ }
216
+ }}
217
+ >
218
+ Show external content
219
+ </Button>
220
+ </div>
221
+
222
+ {!editable && (
223
+ <div className="privacy-toggle">
224
+ <Checkbox
225
+ toggle
226
+ label="Remember my choice"
227
+ id={`remember-choice-${id}`}
228
+ onChange={(ev, { checked }) => {
229
+ setRemember(checked);
211
230
  }}
212
- >
213
- Show external content
214
- </Button>
231
+ checked={remember}
232
+ />
215
233
  </div>
234
+ )}
216
235
 
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>
236
+ <p className="discreet">
237
+ Your choice will be saved in a cookie managed by{' '}
238
+ {config.settings.ownDomain || '.eea.europa.eu'} that will
239
+ expire in {getExpDays()} days.
240
+ </p>
241
+ <p className="discreet">
242
+ {serializeNodes(
243
+ privacy_statement ||
244
+ ProtectionSchema().properties.privacy_statement
245
+ .defaultValue,
229
246
  )}
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>
247
+ </p>
244
248
  </div>
245
249
  </div>
246
- )}
247
- </div>
248
- ) : (
249
- <Placeholder style={{ height: '100%', width: '100%' }}>
250
- <Placeholder.Image rectangular />
251
- </Placeholder>
252
- )}
250
+ </div>
251
+ )}
252
+ </div>
253
253
  </VisibilitySensor>
254
254
  );
255
255
  };
@@ -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;
@@ -8,6 +8,7 @@
8
8
  .enlarge-modal.ui.modal {
9
9
  width: 95% !important;
10
10
  height: 85%;
11
+ overflow-y: auto;
11
12
 
12
13
  .close.icon {
13
14
  top: 1rem;
@@ -22,8 +23,6 @@
22
23
  }
23
24
 
24
25
  .content {
25
- height: 90%;
26
-
27
26
  .js-plotly-plot {
28
27
  width: 100%;
29
28
  height: 100%;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { Container } from 'semantic-ui-react';
3
+ import Map from '@eeacms/volto-embed/Map/Map';
4
+
5
+ const MapView = (props) => {
6
+ return (
7
+ <Container>
8
+ <Map data={props.content.maps} id={props.content['@id']} />
9
+ </Container>
10
+ );
11
+ };
12
+
13
+ export default MapView;
@@ -0,0 +1,5 @@
1
+ import Map from '@eeacms/volto-embed/Map/Map';
2
+
3
+ export default function MapsViewWidget({ id, value }) {
4
+ return <Map data={value} id={id} />;
5
+ }
@@ -0,0 +1,223 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { useIntl, FormattedMessage } from 'react-intl';
3
+ import { Icon } from '@plone/volto/components';
4
+ import { Button, Modal, Grid, Label, Input, Message } from 'semantic-ui-react';
5
+ import { map } from 'lodash';
6
+ import { FormFieldWrapper, InlineForm } from '@plone/volto/components';
7
+ import { addPrivacyProtectionToSchema } from '@eeacms/volto-embed';
8
+ import Map from '@eeacms/volto-embed/Map/Map';
9
+ import { MapsSchema } from '@eeacms/volto-embed/Blocks/Maps/schema';
10
+
11
+ import clearSVG from '@plone/volto/icons/clear.svg';
12
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
13
+ import mapsBlockSVG from '@plone/volto/components/manage/Blocks/Maps/block-maps.svg';
14
+
15
+ function MapEditorModal({ id, onClose, onChange, ...rest }) {
16
+ const intl = useIntl();
17
+ const [value, setValue] = useState(rest.value);
18
+ const [url, setUrl] = useState(rest.value.url);
19
+ const [error, setError] = useState(false);
20
+
21
+ const schema = useMemo(
22
+ () => addPrivacyProtectionToSchema(MapsSchema({ intl })),
23
+ [intl],
24
+ );
25
+
26
+ const placeholder = useMemo(
27
+ () => value.placeholder || intl.messages?.['Enter map Embed Code'],
28
+ [value.placeholder, intl],
29
+ );
30
+
31
+ function getSrc(embed) {
32
+ if (!embed || embed.trim() === 0) {
33
+ setError(true);
34
+ return '';
35
+ }
36
+ // Optimization, don't need the src
37
+ if (!embed.trim().startsWith('<iframe')) {
38
+ return embed;
39
+ }
40
+ const parser = new DOMParser();
41
+ const doc = parser.parseFromString(embed, 'text/html');
42
+ const iframe = doc.getElementsByTagName('iframe');
43
+ if (iframe.length === 0) {
44
+ setError(true);
45
+ return '';
46
+ }
47
+ setError(false);
48
+ return iframe[0].src;
49
+ }
50
+
51
+ function onChangeUrl({ target }) {
52
+ setUrl(target.value);
53
+ }
54
+
55
+ function onKeyDownVariantMenuForm(e) {
56
+ if (e.key === 'Enter') {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ onSubmitUrl();
60
+ } else if (e.key === 'Escape') {
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ // TODO: Do something on ESC key
64
+ }
65
+ }
66
+
67
+ function onSubmitUrl() {
68
+ setValue({
69
+ ...value,
70
+ url: getSrc(url),
71
+ });
72
+ }
73
+
74
+ return (
75
+ <Modal open={true} size="fullscreen" className="chart-editor-modal">
76
+ <Modal.Content scrolling>
77
+ <div className="block maps align center">
78
+ {value.url && (
79
+ <Grid>
80
+ <Grid.Column
81
+ mobile={4}
82
+ tablet={4}
83
+ computer={4}
84
+ className="tableau-editor-column"
85
+ >
86
+ <InlineForm
87
+ schema={schema}
88
+ onChangeField={(id, fieldValue) => {
89
+ if (id === 'url' && fieldValue !== url) {
90
+ setUrl(fieldValue);
91
+ }
92
+ setValue((value) => ({
93
+ ...value,
94
+ [id]: fieldValue,
95
+ }));
96
+ }}
97
+ formData={value}
98
+ />
99
+ </Grid.Column>
100
+ <Grid.Column
101
+ mobile={8}
102
+ tablet={8}
103
+ computer={8}
104
+ className="tableau-visualization-column"
105
+ >
106
+ <Map data={value} id={id} />
107
+ </Grid.Column>
108
+ </Grid>
109
+ )}
110
+ {!value.url && (
111
+ <Message>
112
+ <center>
113
+ <img src={mapsBlockSVG} alt="" />
114
+ <div className="toolbar-inner">
115
+ <Input
116
+ onKeyDown={onKeyDownVariantMenuForm}
117
+ onChange={onChangeUrl}
118
+ placeholder={placeholder}
119
+ value={url}
120
+ // Prevents propagation to the Dropzone and the opening
121
+ // of the upload browser dialog
122
+ onClick={(e) => e.stopPropagation()}
123
+ />
124
+ {url && (
125
+ <Button.Group>
126
+ <Button
127
+ basic
128
+ className="cancel"
129
+ onClick={(e) => {
130
+ e.stopPropagation();
131
+ setUrl('');
132
+ }}
133
+ >
134
+ <Icon name={clearSVG} size="30px" />
135
+ </Button>
136
+ </Button.Group>
137
+ )}
138
+ <Button.Group>
139
+ <Button
140
+ basic
141
+ primary
142
+ onClick={(e) => {
143
+ e.stopPropagation();
144
+ onSubmitUrl();
145
+ }}
146
+ >
147
+ <Icon name={aheadSVG} size="30px" />
148
+ </Button>
149
+ </Button.Group>
150
+ </div>
151
+ <div className="message-text">
152
+ <FormattedMessage
153
+ id="Please enter the Embed Code provided by Google Maps -> Share -> Embed map. It should contain the <iframe> code on it."
154
+ defaultMessage="Please enter the Embed Code provided by Google Maps -> Share -> Embed map. It should contain the <iframe> code on it."
155
+ />
156
+ {error && (
157
+ <div style={{ color: 'red' }}>
158
+ <FormattedMessage
159
+ id="Embed code error, please follow the instructions and try again."
160
+ defaultMessage="Embed code error, please follow the instructions and try again."
161
+ />
162
+ </div>
163
+ )}
164
+ </div>
165
+ </center>
166
+ </Message>
167
+ )}
168
+ </div>
169
+ </Modal.Content>
170
+ <Modal.Actions>
171
+ <Button primary floated="right" onClick={() => onChange(value)}>
172
+ Apply changes
173
+ </Button>
174
+ <Button floated="right" onClick={onClose}>
175
+ Close
176
+ </Button>
177
+ </Modal.Actions>
178
+ </Modal>
179
+ );
180
+ }
181
+
182
+ export default function MapsWidget(props) {
183
+ const { id, title, description, error, value } = props;
184
+ const [mapEditor, setMapEditor] = useState(false);
185
+
186
+ function onChange(value) {
187
+ props.onChange(props.id, value);
188
+ setMapEditor(false);
189
+ }
190
+
191
+ return (
192
+ <FormFieldWrapper {...props} columns={1}>
193
+ <div className="wrapper">
194
+ <label htmlFor={`field-${id}`}>{title}</label>
195
+ <Button
196
+ floated="right"
197
+ onClick={(e) => {
198
+ e.preventDefault();
199
+ e.stopPropagation();
200
+ setMapEditor(true);
201
+ }}
202
+ >
203
+ Open Map Editor
204
+ </Button>
205
+ </div>
206
+ {description && <p className="help">{description}</p>}
207
+ {value.url && <Map {...props} data={value} />}
208
+ {mapEditor && (
209
+ <MapEditorModal
210
+ id={id}
211
+ value={value}
212
+ onChange={onChange}
213
+ onClose={() => setMapEditor(false)}
214
+ />
215
+ )}
216
+ {map(error, (message) => (
217
+ <Label key={message} basic color="red" pointing>
218
+ {message}
219
+ </Label>
220
+ ))}
221
+ </FormFieldWrapper>
222
+ );
223
+ }
package/src/index.js CHANGED
@@ -1,10 +1,17 @@
1
1
  import installBlocks from './Blocks';
2
+ import MapView from './Views/MapView';
3
+ import MapsViewWidget from './Widgets/MapsViewWidget';
4
+ import MapsWidget from './Widgets/MapsWidget';
2
5
  export {
3
6
  PrivacyProtection,
4
7
  addPrivacyProtectionToSchema,
5
8
  } from './PrivacyProtection';
6
9
 
7
10
  export default function applyConfig(config) {
11
+ config.views.contentTypesViews.map = MapView;
12
+ config.widgets.id.maps = MapsWidget;
13
+ config.widgets.views.id.maps = MapsViewWidget;
14
+
8
15
  config.settings.allowed_cors_destinations = [
9
16
  ...config.settings.allowed_cors_destinations,
10
17
  'screenshot.eea.europa.eu',