@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.
@@ -0,0 +1,96 @@
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 { Enlarge, FigureNote, MoreInfo, Share, Sources } from '.';
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
+ jest.mock('@plone/volto-slate/editor/render', () => ({
19
+ __esModule: true,
20
+ serializeNodes: (nodes) => {
21
+ return nodes.map((node, index) => {
22
+ const Tag = node.type;
23
+ return (
24
+ <Tag key={`node-${index}`}>
25
+ {node.children.map((item, index) => (
26
+ <span key={`item-${index}`}>{item.text}</span>
27
+ ))}
28
+ </Tag>
29
+ );
30
+ });
31
+ },
32
+ serializeNodesToText: (nodes) => {
33
+ return nodes
34
+ .reduce((texts, node) => {
35
+ if (node.children) {
36
+ node.children.forEach((item) => {
37
+ texts.push(item.text);
38
+ });
39
+ }
40
+ return texts;
41
+ }, [])
42
+ .join('');
43
+ },
44
+ }));
45
+
46
+ config.blocks.blocksConfig = {
47
+ ...config.blocks.blocksConfig,
48
+ maps: {
49
+ id: 'maps',
50
+ title: 'Maps',
51
+ group: 'media',
52
+ extensions: {},
53
+ variations: [],
54
+ restricted: false,
55
+ mostUsed: true,
56
+ sidebarTab: 1,
57
+ security: {
58
+ addPermission: [],
59
+ view: [],
60
+ },
61
+ },
62
+ };
63
+
64
+ test('renders toolbar components', () => {
65
+ const store = mockStore({
66
+ intl: {
67
+ locale: 'en',
68
+ messages: {},
69
+ },
70
+ content: {
71
+ create: {},
72
+ },
73
+ connected_data_parameters: {},
74
+ });
75
+ const component = renderer.create(
76
+ <Provider store={store}>
77
+ <div className="visualization-toolbar">
78
+ <div className="left-col">
79
+ <FigureNote
80
+ notes={[{ type: 'p', children: [{ text: 'This is a note' }] }]}
81
+ />
82
+ <Sources sources={[]} />
83
+ <MoreInfo href={'/path/to/embeded/content'} />
84
+ </div>
85
+ <div className="right-col">
86
+ <Enlarge className="enlarge-embed-maps">
87
+ <div>Some enlarged content</div>
88
+ </Enlarge>
89
+ <Share href="/path/to/embeded/content" />
90
+ </div>
91
+ </div>
92
+ </Provider>,
93
+ );
94
+ const json = component.toJSON();
95
+ expect(json).toMatchSnapshot();
96
+ });
@@ -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 EmbedMap from '@eeacms/volto-embed/EmbedMap/EmbedMap';
4
+
5
+ const MapView = (props) => {
6
+ return (
7
+ <Container>
8
+ <EmbedMap data={props.content.maps} id={props.content['@id']} />
9
+ </Container>
10
+ );
11
+ };
12
+
13
+ export default MapView;
@@ -0,0 +1,68 @@
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 MapView from './MapView';
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
+ <MapView
50
+ content={{
51
+ maps: {
52
+ '@id': '/path/to/map',
53
+ with_notes: false,
54
+ with_sources: false,
55
+ with_more_info: true,
56
+ with_share: true,
57
+ with_enlarge: true,
58
+ url:
59
+ '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',
60
+ useVisibilitySensor: false,
61
+ },
62
+ }}
63
+ />
64
+ </Provider>,
65
+ );
66
+ const json = component.toJSON();
67
+ expect(json).toMatchSnapshot();
68
+ });
@@ -0,0 +1,5 @@
1
+ import EmbedMap from '@eeacms/volto-embed/EmbedMap/EmbedMap';
2
+
3
+ export default function MapsViewWidget({ id, value }) {
4
+ return <EmbedMap 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 EmbedMap from '@eeacms/volto-embed/EmbedMap/EmbedMap';
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
+ <EmbedMap 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 && <EmbedMap {...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/helpers.js ADDED
@@ -0,0 +1,16 @@
1
+ import { pick } from 'lodash';
2
+
3
+ export function pickMetadata(content) {
4
+ return {
5
+ ...pick(content, [
6
+ '@id',
7
+ 'title',
8
+ 'data_provenance',
9
+ 'figure_note',
10
+ 'other_organisations',
11
+ 'temporan_coverage',
12
+ 'publisher',
13
+ 'geo_coverage',
14
+ ]),
15
+ };
16
+ }
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',
@@ -1,119 +0,0 @@
1
- /* eslint-disable no-unused-vars */
2
- import React from 'react';
3
- import config from '@plone/volto/registry';
4
- import { render, fireEvent, screen } from '@testing-library/react';
5
- import { Provider } from 'react-intl-redux';
6
- import configureStore from 'redux-mock-store';
7
- import Edit from './Edit';
8
- import '@testing-library/jest-dom/extend-expect';
9
-
10
- const mockStore = configureStore();
11
-
12
- const store = mockStore(() => ({
13
- connected_data_parameters: {},
14
- router: {
15
- location: {
16
- pathname: '',
17
- },
18
- },
19
- intl: {
20
- locale: 'en',
21
- messages: {},
22
- },
23
- }));
24
-
25
- config.blocks.blocksConfig = {
26
- ...config.blocks.blocksConfig,
27
- maps: {
28
- id: 'maps',
29
- title: 'Maps',
30
- group: 'media',
31
- extensions: {},
32
- variations: [],
33
- restricted: false,
34
- mostUsed: true,
35
- sidebarTab: 1,
36
- security: {
37
- addPermission: [],
38
- view: [],
39
- },
40
- },
41
- };
42
-
43
- jest.mock(
44
- '@eeacms/volto-embed/PrivacyProtection/PrivacyProtection',
45
- () => ({ children }) => children,
46
- );
47
-
48
- describe('Edit', () => {
49
- it('renders without crashing', () => {
50
- render(
51
- <Provider store={store}>
52
- <Edit
53
- selected={false}
54
- block="block"
55
- index={1}
56
- data={{
57
- '@type': 'maps',
58
- url:
59
- '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',
60
- }}
61
- pathname="/"
62
- onChangeBlock={() => {}}
63
- onSelectBlock={() => {}}
64
- onDeleteBlock={() => {}}
65
- onFocusPreviousBlock={() => {}}
66
- onFocusNextBlock={() => {}}
67
- handleKeyDown={() => {}}
68
- />
69
- </Provider>,
70
- );
71
- });
72
-
73
- it('submits url when button is clicked', async () => {
74
- const url =
75
- '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';
76
-
77
- const { container, getByPlaceholderText } = render(
78
- <Provider store={store}>
79
- <Edit
80
- selected={false}
81
- block="block"
82
- index={1}
83
- data={{
84
- '@type': 'maps',
85
- dataprotection: {
86
- privacy_statement: 'test',
87
- },
88
- }}
89
- pathname="/"
90
- onChangeBlock={() => {}}
91
- onSelectBlock={() => {}}
92
- onDeleteBlock={() => {}}
93
- onFocusPreviousBlock={() => {}}
94
- onFocusNextBlock={() => {}}
95
- handleKeyDown={() => {}}
96
- />
97
- </Provider>,
98
- );
99
-
100
- const input = getByPlaceholderText('Enter map Embed Code');
101
-
102
- fireEvent.click(input);
103
- fireEvent.change(input, { target: { value: url } });
104
- // screen.debug();
105
- // expect(input.value).toBe(url);
106
-
107
- // fireEvent.click(container.querySelector('button.cancel'));
108
-
109
- // fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
110
- // fireEvent.keyDown(input, { key: 'Escape', code: 'Escape' });
111
- // fireEvent.keyDown(input, { key: 'KeyA', code: 'KeyA' });
112
-
113
- // fireEvent.change(input, { target: { value: '<iframe src="test"/>' } });
114
- // fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
115
-
116
- // const button = container.querySelector('button.ui.basic.primary.button');
117
- // fireEvent.click(button);
118
- });
119
- });