@eeacms/volto-embed 10.0.3 → 10.1.1

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,26 @@ 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
+ ### [10.1.1](https://github.com/eea/volto-embed/compare/10.1.0...10.1.1) - 11 October 2024
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: Increase wait time EEA Screenshooter when saving preview image - refs #277935 [dobri1408 - [`644e41f`](https://github.com/eea/volto-embed/commit/644e41f06f22c268cef967d9829eac1ca3035ac3)]
12
+
13
+ ### [10.1.0](https://github.com/eea/volto-embed/compare/10.0.3...10.1.0) - 13 September 2024
14
+
15
+ #### :hammer_and_wrench: Others
16
+
17
+ - Update version [dobri1408 - [`21e7852`](https://github.com/eea/volto-embed/commit/21e7852cfa27bedd7e3670402bf89d7f3b6fdc06)]
18
+ - cleanup preview image loaded [dobri1408 - [`b3e60ff`](https://github.com/eea/volto-embed/commit/b3e60ffedd7e7f7790afbc76e99df4e001e57550)]
19
+ - double check preview [dobri1408 - [`35c6210`](https://github.com/eea/volto-embed/commit/35c621031b4cd25777ac19a2789eac08ffd48176)]
20
+ - tests [dobri1408 - [`70c3620`](https://github.com/eea/volto-embed/commit/70c3620057ffd203105b80ff1c9121a39947c212)]
21
+ - tests [dobri1408 - [`8aa1bfe`](https://github.com/eea/volto-embed/commit/8aa1bfe536968b42cd76abc91b685b28ce485e28)]
22
+ - tests [dobri1408 - [`879f6a7`](https://github.com/eea/volto-embed/commit/879f6a70d329228d974f347cea983621194433a3)]
23
+ - Create preview_image.test.js [dobri1408 - [`fbb7cdb`](https://github.com/eea/volto-embed/commit/fbb7cdb69c1d43a928024d2c83a0dee87faae7af)]
24
+ - Update MapsWidget.jsx [dobri1408 - [`cd46123`](https://github.com/eea/volto-embed/commit/cd46123d39c71d034a2aa9919ad04e7364a08e8d)]
25
+ - Update MapsWidget.jsx [dobri1408 - [`e36c6a9`](https://github.com/eea/volto-embed/commit/e36c6a9c0269622cabf2069d6b8cd082cfbe879b)]
26
+ - preview image middlware [dobri1408 - [`5a0f8e9`](https://github.com/eea/volto-embed/commit/5a0f8e9231d8f5f760dcbd82bbab4a84778d1202)]
7
27
  ### [10.0.3](https://github.com/eea/volto-embed/compare/10.0.2...10.0.3) - 24 July 2024
8
28
 
9
29
  #### :bug: Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-embed",
3
- "version": "10.0.3",
3
+ "version": "10.1.1",
4
4
  "description": "Embed external content",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -0,0 +1,34 @@
1
+ import { createImageUrl } from './helpers';
2
+
3
+ describe('createImageUrl', () => {
4
+ it('should create a valid image URL from the base64 encoded data', () => {
5
+ // Mock data for the test
6
+ const mockResult = {
7
+ data: 'aGVsbG8gd29ybGQ=', // "hello world" in base64
8
+ 'content-type': 'image/png',
9
+ };
10
+
11
+ // Mock the atob function
12
+ jest.spyOn(window, 'atob').mockImplementation(() => 'hello world');
13
+
14
+ // Mock the URL.createObjectURL function
15
+ const mockUrl = 'blob:http://localhost:3000/some-url';
16
+ global.URL.createObjectURL = jest.fn().mockReturnValue(mockUrl);
17
+
18
+ // Call the function
19
+ const imageUrl = createImageUrl(mockResult);
20
+
21
+ // Assertions
22
+ expect(window.atob).toHaveBeenCalledWith(mockResult.data);
23
+ expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob));
24
+ expect(imageUrl).toBe(mockUrl);
25
+
26
+ // Verify the Blob creation
27
+ const blobArgs = URL.createObjectURL.mock.calls[0][0];
28
+ expect(blobArgs.type).toBe(mockResult['content-type']);
29
+ expect(blobArgs.size).toBe(11); // "hello world" is 11 bytes
30
+
31
+ // Clean up mocks
32
+ jest.restoreAllMocks();
33
+ });
34
+ });
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen } from '@testing-library/react';
3
+ import Sources from './Sources';
4
+ import '@testing-library/jest-dom/extend-expect';
5
+
6
+ const mockSources = [
7
+ {
8
+ chart_source: 'Source 1',
9
+ chart_source_link: 'http://example.com/source1',
10
+ organisation: 'Organisation 1',
11
+ title: 'Title 1',
12
+ link: 'http://example.com/link1',
13
+ },
14
+ {
15
+ chart_source: 'Source 2',
16
+ chart_source_link: 'http://example.com/source2',
17
+ organisation: 'Organisation 2',
18
+ title: 'Title 2',
19
+ link: 'http://example.com/link2',
20
+ },
21
+ ];
22
+
23
+ describe('Sources', () => {
24
+ it('renders the Sources button', () => {
25
+ render(<Sources sources={mockSources} />);
26
+ expect(screen.getByText('Sources')).toBeInTheDocument();
27
+ });
28
+
29
+ it('opens and closes the popup on click', () => {
30
+ render(<Sources sources={mockSources} />);
31
+
32
+ const triggerButton = screen.getByText('Sources');
33
+
34
+ expect(screen.queryByText('Source 1')).toBeInTheDocument();
35
+
36
+ fireEvent.click(triggerButton);
37
+ expect(screen.getByText('Source 1')).toBeInTheDocument();
38
+ expect(screen.getByText('Source 2')).toBeInTheDocument();
39
+
40
+ fireEvent.click(triggerButton);
41
+ });
42
+
43
+ it('renders sources correctly', () => {
44
+ render(<Sources sources={mockSources} />);
45
+
46
+ const triggerButton = screen.getByText('Sources');
47
+ fireEvent.click(triggerButton);
48
+
49
+ expect(screen.getByText('Source 1')).toBeInTheDocument();
50
+ expect(screen.getByText('Source 2')).toBeInTheDocument();
51
+
52
+ expect(screen.getByText('Source 1').closest('a')).toHaveAttribute(
53
+ 'href',
54
+ 'http://example.com/source1',
55
+ );
56
+ expect(screen.getByText('Source 2').closest('a')).toHaveAttribute(
57
+ 'href',
58
+ 'http://example.com/source2',
59
+ );
60
+ });
61
+
62
+ it('renders a message when there are no sources', () => {
63
+ render(<Sources sources={[]} />);
64
+
65
+ const triggerButton = screen.getByText('Sources');
66
+ fireEvent.click(triggerButton);
67
+
68
+ expect(
69
+ screen.getByText('Data provenance is not set for this visualization.'),
70
+ ).toBeInTheDocument();
71
+ });
72
+ });
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { Provider } from 'react-redux';
4
+ import configureStore from 'redux-mock-store';
5
+ import MapsViewWidget from './MapsViewWidget';
6
+ import EmbedMap from '@eeacms/volto-embed/EmbedMap/EmbedMap';
7
+ import { pickMetadata } from '@eeacms/volto-embed/helpers';
8
+ import '@testing-library/jest-dom/extend-expect'; // Importă jest-dom
9
+
10
+ jest.mock('@eeacms/volto-embed/EmbedMap/EmbedMap', () =>
11
+ jest.fn(() => <div>Mocked EmbedMap</div>),
12
+ );
13
+ jest.mock('@eeacms/volto-embed/helpers', () => ({
14
+ pickMetadata: jest.fn(),
15
+ }));
16
+
17
+ const mockStore = configureStore([]);
18
+
19
+ describe('MapsViewWidget', () => {
20
+ let store;
21
+
22
+ beforeEach(() => {
23
+ store = mockStore({
24
+ content: {
25
+ data: {
26
+ metadata: {
27
+ title: 'Test Title',
28
+ description: 'Test Description',
29
+ },
30
+ },
31
+ },
32
+ });
33
+ });
34
+
35
+ it('should render EmbedMap with the correct data props', () => {
36
+ const mockValue = {
37
+ map_url: 'http://example.com/map',
38
+ };
39
+ const mockId = 'map-widget-1';
40
+
41
+ pickMetadata.mockReturnValue({
42
+ title: 'Test Title',
43
+ description: 'Test Description',
44
+ });
45
+
46
+ const { getByText } = render(
47
+ <Provider store={store}>
48
+ <MapsViewWidget id={mockId} value={mockValue} />
49
+ </Provider>,
50
+ );
51
+
52
+ expect(EmbedMap).toHaveBeenCalledWith(
53
+ {
54
+ data: {
55
+ map_url: 'http://example.com/map',
56
+ title: 'Test Title',
57
+ description: 'Test Description',
58
+ with_share: true,
59
+ },
60
+ id: mockId,
61
+ },
62
+ {},
63
+ );
64
+
65
+ expect(getByText('Mocked EmbedMap')).toBeInTheDocument(); // Verifică dacă elementul este în document
66
+ });
67
+
68
+ it('should handle empty value prop correctly', () => {
69
+ const mockId = 'map-widget-2';
70
+
71
+ pickMetadata.mockReturnValue({
72
+ title: 'Test Title',
73
+ description: 'Test Description',
74
+ });
75
+
76
+ const { getByText } = render(
77
+ <Provider store={store}>
78
+ <MapsViewWidget id={mockId} value={null} />
79
+ </Provider>,
80
+ );
81
+
82
+ expect(EmbedMap).toHaveBeenCalledWith(
83
+ {
84
+ data: {
85
+ title: 'Test Title',
86
+ description: 'Test Description',
87
+ with_share: true,
88
+ },
89
+ id: mockId,
90
+ },
91
+ {},
92
+ );
93
+
94
+ expect(getByText('Mocked EmbedMap')).toBeInTheDocument(); // Verifică dacă elementul este în document
95
+ });
96
+ });
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useMemo, useState, useEffect } from 'react';
2
2
  import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
3
3
  import { Icon } from '@plone/volto/components';
4
4
  import { Button, Modal, Grid, Label, Input, Message } from 'semantic-ui-react';
@@ -7,6 +7,7 @@ import { FormFieldWrapper, InlineForm } from '@plone/volto/components';
7
7
  import { addPrivacyProtectionToSchema } from '@eeacms/volto-embed';
8
8
  import EmbedMap from '@eeacms/volto-embed/EmbedMap/EmbedMap';
9
9
  import { MapsSchema } from '@eeacms/volto-embed/Blocks/Maps/schema';
10
+ import { getBaseUrl } from '@plone/volto/helpers';
10
11
 
11
12
  import clearSVG from '@plone/volto/icons/clear.svg';
12
13
  import aheadSVG from '@plone/volto/icons/ahead.svg';
@@ -19,6 +20,17 @@ const messages = defineMessages({
19
20
  },
20
21
  });
21
22
 
23
+ function blobToBase64(blob) {
24
+ return new Promise((resolve, reject) => {
25
+ const reader = new FileReader();
26
+ reader.onloadend = () => {
27
+ resolve(reader.result);
28
+ };
29
+ reader.onerror = reject;
30
+ reader.readAsDataURL(blob);
31
+ });
32
+ }
33
+
22
34
  function MapEditorModal({ id, onClose, onChange, ...rest }) {
23
35
  const intl = useIntl();
24
36
  const [value, setValue] = useState(rest.value || {});
@@ -189,14 +201,37 @@ function MapEditorModal({ id, onClose, onChange, ...rest }) {
189
201
  }
190
202
 
191
203
  export default function MapsWidget(props) {
192
- const { id, title, description, error, value } = props;
204
+ const { id, title, description, error, value, onChange } = props;
193
205
  const [mapEditor, setMapEditor] = useState(false);
194
206
 
195
- function onChange(value) {
196
- props.onChange(props.id, value);
207
+ function onChangeMap(value) {
208
+ onChange(id, value);
197
209
  setMapEditor(false);
198
210
  }
199
211
 
212
+ useEffect(() => {
213
+ if (value && value.url && value.preview_url_loaded !== value.url) {
214
+ fetch(
215
+ `${getBaseUrl(
216
+ '',
217
+ )}/cors-proxy/https://screenshot.eea.europa.eu/api/v1/retrieve_image_for_url?url=${encodeURIComponent(
218
+ value.url,
219
+ )}&w=1920&h=1000&waitfor=8000`,
220
+ )
221
+ .then((e) => e.blob())
222
+ .then((myBlob) => {
223
+ blobToBase64(myBlob).then((base64String) => {
224
+ onChange(id, {
225
+ ...value,
226
+ preview: base64String,
227
+ preview_url_loaded: value.url,
228
+ });
229
+ });
230
+ })
231
+ .catch(() => {});
232
+ }
233
+ }, [value, onChange, id]);
234
+
200
235
  return (
201
236
  <FormFieldWrapper {...props} columns={1}>
202
237
  <div className="wrapper">
@@ -218,7 +253,7 @@ export default function MapsWidget(props) {
218
253
  <MapEditorModal
219
254
  id={id}
220
255
  value={value || {}}
221
- onChange={onChange}
256
+ onChange={onChangeMap}
222
257
  onClose={() => setMapEditor(false)}
223
258
  />
224
259
  )}
package/src/index.js CHANGED
@@ -2,6 +2,7 @@ import installBlocks from './Blocks';
2
2
  import MapView from './Views/MapView';
3
3
  import MapsViewWidget from './Widgets/MapsViewWidget';
4
4
  import MapsWidget from './Widgets/MapsWidget';
5
+ import { preview_image } from './middlewares/preview_image';
5
6
  export {
6
7
  PrivacyProtection,
7
8
  addPrivacyProtectionToSchema,
@@ -16,5 +17,9 @@ export default function applyConfig(config) {
16
17
  ...config.settings.allowed_cors_destinations,
17
18
  'screenshot.eea.europa.eu',
18
19
  ];
20
+ config.settings.storeExtenders = [
21
+ ...(config.settings.storeExtenders || []),
22
+ preview_image,
23
+ ];
19
24
  return [installBlocks].reduce((acc, apply) => apply(acc), config);
20
25
  }
@@ -0,0 +1,93 @@
1
+ import {
2
+ CREATE_CONTENT,
3
+ UPDATE_CONTENT,
4
+ } from '@plone/volto/constants/ActionTypes';
5
+
6
+ const cleanAction = (action) => {
7
+ if (action?.request?.data?.maps) {
8
+ const mapVisualizationData = {
9
+ ...action.request.data.maps,
10
+ };
11
+ if (mapVisualizationData.preview && mapVisualizationData.preview_url_loaded)
12
+ delete mapVisualizationData.preview;
13
+ delete mapVisualizationData.preview_url_loaded;
14
+
15
+ return {
16
+ ...action,
17
+ request: {
18
+ ...action.request,
19
+ data: {
20
+ ...action.request.data,
21
+
22
+ maps: mapVisualizationData,
23
+ },
24
+ },
25
+ };
26
+ } else return action;
27
+ };
28
+
29
+ export const preview_image = (middlewares) => [
30
+ (store) => (next) => async (action) => {
31
+ if (![CREATE_CONTENT, UPDATE_CONTENT].includes(action.type)) {
32
+ return next(action);
33
+ }
34
+ const state = store.getState();
35
+ const contentData = state.content.data;
36
+ const lastPreviewImage = Object.keys(action?.request?.data).includes(
37
+ 'preview_image',
38
+ )
39
+ ? action?.request?.data.preview_image
40
+ : contentData?.preview_image;
41
+ const type = action?.request?.data?.['@type'] || contentData['@type'];
42
+
43
+ if (
44
+ !contentData ||
45
+ type !== 'map_interactive' ||
46
+ contentData.preview_image_saved ||
47
+ !action?.request?.data?.maps?.preview
48
+ ) {
49
+ return next(cleanAction(action));
50
+ }
51
+
52
+ if (
53
+ lastPreviewImage &&
54
+ lastPreviewImage.filename !==
55
+ 'preview_image_generated_map_interactive.png'
56
+ ) {
57
+ return next(cleanAction(action));
58
+ }
59
+
60
+ try {
61
+ const previewImage = {
62
+ preview_image: {
63
+ data: action.request.data.maps.preview.split(',')[1],
64
+ encoding: 'base64',
65
+ 'content-type': 'image/png',
66
+ filename: 'preview_image_generated_map_interactive.png',
67
+ },
68
+ preview_image_saved: true,
69
+ };
70
+
71
+ const mapVisualizationData = {
72
+ ...action.request.data.maps,
73
+ };
74
+ delete mapVisualizationData.preview;
75
+ delete mapVisualizationData.preview_url_loaded;
76
+
77
+ return next({
78
+ ...action,
79
+ request: {
80
+ ...action.request,
81
+ data: {
82
+ ...action.request.data,
83
+ ...previewImage,
84
+ maps: mapVisualizationData,
85
+ },
86
+ },
87
+ });
88
+ } catch (error) {
89
+ return next(action);
90
+ }
91
+ },
92
+ ...middlewares,
93
+ ];
@@ -0,0 +1,49 @@
1
+ import { preview_image } from './preview_image';
2
+ describe('preview_image middleware', () => {
3
+ let store;
4
+ let next;
5
+ let action;
6
+ let middlewares;
7
+
8
+ beforeEach(() => {
9
+ store = {
10
+ getState: jest.fn(() => ({
11
+ content: {
12
+ data: {
13
+ '@type': 'map_interactive',
14
+ preview_image: 'existing_image.png',
15
+ preview_image_saved: false,
16
+ },
17
+ },
18
+ })),
19
+ };
20
+ next = jest.fn();
21
+ middlewares = [];
22
+ });
23
+
24
+ it('existing image', () => {
25
+ action = { type: 'UPDATE_CONTENT', request: { data: {} } };
26
+ const middleware = preview_image(middlewares)[0]; // Accesăm prima funcție din array
27
+ middleware(store)(next)(action); // Executăm funcția de middleware
28
+ expect(next).toHaveBeenCalledWith(action); // Verificăm că funcția next a fost apelată cu acțiunea originală
29
+ });
30
+ it('redo the image', () => {
31
+ store = {
32
+ getState: jest.fn(() => ({
33
+ content: {
34
+ data: {
35
+ '@type': 'map_interactive',
36
+ preview_image: 'preview_image_generated_map_interactive.png',
37
+ preview_image_saved: false,
38
+ },
39
+ },
40
+ })),
41
+ };
42
+ next = jest.fn();
43
+ middlewares = [];
44
+ action = { type: 'UPDATE_CONTENT', request: { data: {} } };
45
+ const middleware = preview_image(middlewares)[0]; // Accesăm prima funcție din array
46
+ middleware(store)(next)(action); // Executăm funcția de middleware
47
+ expect(next).toHaveBeenCalledWith(action); // Verificăm că funcția next a fost apelată cu acțiunea originală
48
+ });
49
+ });