@eeacms/volto-tableau 3.0.3 → 3.0.5
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 +16 -0
- package/README.md +21 -0
- package/package.json +1 -1
- package/src/Blocks/EmbedEEATableauBlock/View.jsx +21 -28
- package/src/Blocks/EmbedEEATableauBlock/schema.js +47 -6
- package/src/ConnectedTableau/ConnectedTableau.jsx +7 -1
- package/src/CustomWidgets/UrlParamsWidget.jsx +29 -0
- package/src/DownloadExtras/TableauDownload.jsx +62 -0
- package/src/DownloadExtras/TableauFullscreen.jsx +78 -0
- package/src/DownloadExtras/TableauShare.jsx +81 -0
- package/src/DownloadExtras/style.less +152 -0
- package/src/Sources/Sources.jsx +13 -11
- package/src/Tableau/View.jsx +20 -11
- package/src/TableauBlock/View.jsx +21 -23
- package/src/TableauBlock/schema.js +2 -20
- package/src/Views/VisualizationView.jsx +9 -10
- package/src/Widgets/VisualizationWidget.jsx +1 -9
- package/src/Widgets/schema.js +26 -38
- package/src/downloadHelpers/downloadHelpers.js +25 -0
- package/src/index.js +2 -0
- package/src/less/tableau.less +17 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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
|
+
### [3.0.5](https://github.com/eea/volto-tableau/compare/3.0.4...3.0.5) - 26 January 2023
|
|
8
|
+
|
|
9
|
+
#### :hammer_and_wrench: Others
|
|
10
|
+
|
|
11
|
+
- Add download options [dana-cfc4 - [`47031b2`](https://github.com/eea/volto-tableau/commit/47031b277cbde58a626076eef9d4fdb42c9400e2)]
|
|
12
|
+
- Remove version chooser [dana-cfc4 - [`34ceaf9`](https://github.com/eea/volto-tableau/commit/34ceaf9a89ea0fcc5b001cc86fd3af709629693a)]
|
|
13
|
+
- Remove height setting, solve no source link bug [dana-cfc4 - [`03216e3`](https://github.com/eea/volto-tableau/commit/03216e3d159ef9cf1b71702a3a409ab41f3cd427)]
|
|
14
|
+
- links to deps [Andrei Grigore - [`4cec1b2`](https://github.com/eea/volto-tableau/commit/4cec1b283a2cad800dbc9081caee2a03847060cd)]
|
|
15
|
+
- Update README.md [dana-cfc4 - [`83e4e7a`](https://github.com/eea/volto-tableau/commit/83e4e7a94892caa42cb5bcc4cb02b923dca87379)]
|
|
16
|
+
- Add privacy protection fields [dana-cfc4 - [`78cb29f`](https://github.com/eea/volto-tableau/commit/78cb29f29859aed7deaf18cb678bd8e689643883)]
|
|
17
|
+
### [3.0.4](https://github.com/eea/volto-tableau/compare/3.0.3...3.0.4) - 18 January 2023
|
|
18
|
+
|
|
19
|
+
#### :hammer_and_wrench: Others
|
|
20
|
+
|
|
21
|
+
- Fix lint issues [dana-cfc4 - [`9bbf908`](https://github.com/eea/volto-tableau/commit/9bbf908195a9e90c606ebc87042ae3c7a44e7973)]
|
|
22
|
+
- Add available fields, remove default version, manage no version selected [dana-cfc4 - [`66e510e`](https://github.com/eea/volto-tableau/commit/66e510e5f2e47e049e157ab046d0499f14b8f700)]
|
|
7
23
|
### [3.0.3](https://github.com/eea/volto-tableau/compare/3.0.2...3.0.3) - 17 January 2023
|
|
8
24
|
|
|
9
25
|
#### :hammer_and_wrench: Others
|
package/README.md
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
[Volto](https://github.com/plone/volto) add-on
|
|
18
18
|
|
|
19
|
+
## Tableau
|
|
20
|
+
|
|
21
|
+
Registers a VisualizationView component for a content type named 'tableau_visualization'.
|
|
22
|
+
|
|
19
23
|
## Features
|
|
20
24
|
|
|
21
25
|
Demo GIF
|
|
@@ -155,6 +159,23 @@ Generic command, does not automatically add the `beta` to version, but you can s
|
|
|
155
159
|
|
|
156
160
|
> Do not keep Pull Requests from develop to master branches open when you are doing beta releases from the develop branch. As long as a PR to master is open, an automatic script will run on every commit and will update both the version and the changelog to a production-ready state - ( MAJOR.MINOR.PATCH mandatory format for version).
|
|
157
161
|
|
|
162
|
+
## Enable Sources
|
|
163
|
+
|
|
164
|
+
https://github.com/eea/eea.coremetadata EEA Core Metadata addon is needed.
|
|
165
|
+
|
|
166
|
+
Sources (Data provenance) should be set on the visualization. To enable this, "EEA Core Metadata" should be checked as behavior on the visualization content-type.
|
|
167
|
+
|
|
168
|
+
controlpanel/dexterity-types/tableau_visualization
|
|
169
|
+
|
|
170
|
+
After this, sources can be added from the visualization edit interface. "EEA core metadata" tab => "Add source"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
## Add a Tableau Visualization
|
|
174
|
+
|
|
175
|
+
1. Add the corresponding content-type. Project should also contain https://github.com/eea/eea.api.dataconnector which hosts the content-type.
|
|
176
|
+
2. Fill in the mandatory fields
|
|
177
|
+
3. "Tableau Widget" section => "Open Tableau Editor"
|
|
178
|
+
4. Provide the necessary settings to create the visualization
|
|
158
179
|
|
|
159
180
|
## How to contribute
|
|
160
181
|
|
package/package.json
CHANGED
|
@@ -36,35 +36,28 @@ const View = (props) => {
|
|
|
36
36
|
<PrivacyProtection data={data} {...props}>
|
|
37
37
|
{data?.vis_url ? (
|
|
38
38
|
<>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</h3>
|
|
47
|
-
</div>
|
|
48
|
-
) : (
|
|
49
|
-
''
|
|
50
|
-
)}
|
|
51
|
-
<ConnectedTableau
|
|
52
|
-
{...props.tableau_visualization}
|
|
53
|
-
id={props.id}
|
|
54
|
-
{...props}
|
|
55
|
-
/>
|
|
56
|
-
</div>
|
|
39
|
+
{tableau_visualization?.general?.url ? (
|
|
40
|
+
<>
|
|
41
|
+
<ConnectedTableau
|
|
42
|
+
{...props.tableau_visualization}
|
|
43
|
+
id={props.id}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
57
46
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
47
|
+
{show_sources &&
|
|
48
|
+
data_provenance &&
|
|
49
|
+
data_provenance?.data?.data_provenance &&
|
|
50
|
+
tableau_visualization ? (
|
|
51
|
+
<Sources sources={data_provenance.data.data_provenance} />
|
|
52
|
+
) : show_sources ? (
|
|
53
|
+
<div>Data provenance is not set in the visualization</div>
|
|
54
|
+
) : (
|
|
55
|
+
''
|
|
56
|
+
)}
|
|
57
|
+
</>
|
|
58
|
+
) : !tableau_visualization?.general?.url ? (
|
|
59
|
+
<div>Url is not set in the visualization</div>
|
|
60
|
+
) : null}
|
|
68
61
|
</>
|
|
69
62
|
) : (
|
|
70
63
|
<div>Please select a visualization from block editor.</div>
|
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
const ProtectionSchema = () => ({
|
|
2
|
+
title: 'Data Protection',
|
|
3
|
+
|
|
4
|
+
fieldsets: [
|
|
5
|
+
{
|
|
6
|
+
id: 'default',
|
|
7
|
+
title: 'Default',
|
|
8
|
+
fields: [
|
|
9
|
+
'privacy_statement',
|
|
10
|
+
'privacy_cookie_key',
|
|
11
|
+
'enabled',
|
|
12
|
+
'background_image',
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
|
|
17
|
+
properties: {
|
|
18
|
+
privacy_statement: {
|
|
19
|
+
title: 'Privacy statement',
|
|
20
|
+
description: 'Defined in template. Change only if necessary',
|
|
21
|
+
widget: 'slate_richtext',
|
|
22
|
+
className: 'slate-Widget',
|
|
23
|
+
},
|
|
24
|
+
privacy_cookie_key: {
|
|
25
|
+
title: 'Privacy cookie key',
|
|
26
|
+
description: 'Use default for Tableau, otherwise change',
|
|
27
|
+
defaultValue: 'tableau',
|
|
28
|
+
},
|
|
29
|
+
enabled: {
|
|
30
|
+
title: 'Data protection disclaimer enabled',
|
|
31
|
+
description: 'Enable/disable the privacy protection',
|
|
32
|
+
type: 'boolean',
|
|
33
|
+
},
|
|
34
|
+
background_image: {
|
|
35
|
+
title: 'Tableau preview image',
|
|
36
|
+
widget: 'file',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
required: [],
|
|
41
|
+
});
|
|
42
|
+
|
|
1
43
|
const Schema = (props) => {
|
|
2
44
|
return {
|
|
3
45
|
title: 'Embed EEA Tableau',
|
|
@@ -5,7 +47,7 @@ const Schema = (props) => {
|
|
|
5
47
|
{
|
|
6
48
|
id: 'default',
|
|
7
49
|
title: 'Default',
|
|
8
|
-
fields: ['vis_url', '
|
|
50
|
+
fields: ['vis_url', 'show_sources', 'dataprotection'],
|
|
9
51
|
},
|
|
10
52
|
],
|
|
11
53
|
properties: {
|
|
@@ -13,15 +55,14 @@ const Schema = (props) => {
|
|
|
13
55
|
widget: 'object_by_path',
|
|
14
56
|
title: 'Visualization',
|
|
15
57
|
},
|
|
16
|
-
height: {
|
|
17
|
-
title: 'Height',
|
|
18
|
-
type: 'number',
|
|
19
|
-
default: 450,
|
|
20
|
-
},
|
|
21
58
|
show_sources: {
|
|
22
59
|
title: 'Toggle sources',
|
|
23
60
|
type: 'boolean',
|
|
24
61
|
},
|
|
62
|
+
dataprotection: {
|
|
63
|
+
widget: 'object',
|
|
64
|
+
schema: ProtectionSchema(),
|
|
65
|
+
},
|
|
25
66
|
},
|
|
26
67
|
|
|
27
68
|
required: ['vis_url'],
|
|
@@ -6,6 +6,13 @@ const ConnectedTableau = (props) => {
|
|
|
6
6
|
const [loaded, setLoaded] = React.useState(null);
|
|
7
7
|
return (
|
|
8
8
|
<div className="tableau-block">
|
|
9
|
+
{loaded && props.mode === 'edit' ? (
|
|
10
|
+
<div className="tableau-info">
|
|
11
|
+
<h3 className="tableau-version">== Tableau ==</h3>
|
|
12
|
+
</div>
|
|
13
|
+
) : (
|
|
14
|
+
''
|
|
15
|
+
)}
|
|
9
16
|
<Tableau
|
|
10
17
|
error={error}
|
|
11
18
|
loaded={loaded}
|
|
@@ -13,7 +20,6 @@ const ConnectedTableau = (props) => {
|
|
|
13
20
|
setLoaded={setLoaded}
|
|
14
21
|
data={{ ...props?.general, ...props?.options, ...props?.extraOptions }}
|
|
15
22
|
url={props?.general?.url}
|
|
16
|
-
version={props?.general?.version}
|
|
17
23
|
{...props}
|
|
18
24
|
/>
|
|
19
25
|
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import '@eeacms/volto-tableau/less/tableau.less';
|
|
3
|
+
|
|
4
|
+
const UrlParamsWidget = () => {
|
|
5
|
+
const fields = [
|
|
6
|
+
'embed',
|
|
7
|
+
'isGuestRedirectFromVizportal',
|
|
8
|
+
'showShareOptions',
|
|
9
|
+
'jsdebug',
|
|
10
|
+
'sheetname',
|
|
11
|
+
'display_count',
|
|
12
|
+
'showVizHome',
|
|
13
|
+
'origin',
|
|
14
|
+
'device',
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="availableFieldsContainer">
|
|
19
|
+
<p className="availableFieldsTitle">Available Fields:</p>
|
|
20
|
+
{fields.map((field) => (
|
|
21
|
+
<p className="availableFields">
|
|
22
|
+
<strong>{field}</strong>
|
|
23
|
+
</p>
|
|
24
|
+
))}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default UrlParamsWidget;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Popup, Button, Modal } from 'semantic-ui-react';
|
|
3
|
+
import { Icon } from '@plone/volto/components';
|
|
4
|
+
import downloadSVG from '@plone/volto/icons/download.svg';
|
|
5
|
+
|
|
6
|
+
const TableauDownload = (props) => {
|
|
7
|
+
const [open, setOpen] = React.useState(false);
|
|
8
|
+
const viz = props.viz || {};
|
|
9
|
+
|
|
10
|
+
const exportImage = () => {
|
|
11
|
+
viz.showExportImageDialog();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const exportToCSV = () => {
|
|
15
|
+
viz.showExportCrossTabDialog();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const exportToExcel = () => {
|
|
19
|
+
viz.exportCrossTabToExcel();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<Popup
|
|
25
|
+
basic
|
|
26
|
+
className="tableau-download-dialog"
|
|
27
|
+
position="top center"
|
|
28
|
+
on="click"
|
|
29
|
+
trigger={
|
|
30
|
+
<div className="toolbar-button-wrapper">
|
|
31
|
+
<Button className="toolbar-button" title="Download">
|
|
32
|
+
<Icon name={downloadSVG} size="26px" />
|
|
33
|
+
</Button>
|
|
34
|
+
<span className="btn-text">Save</span>
|
|
35
|
+
</div>
|
|
36
|
+
}
|
|
37
|
+
>
|
|
38
|
+
<Popup.Header>Download</Popup.Header>
|
|
39
|
+
<Popup.Content>
|
|
40
|
+
<p>Select your file format.</p>
|
|
41
|
+
<Button onClick={exportImage}>Image</Button>
|
|
42
|
+
<Button onClick={exportToCSV}>CSV</Button>
|
|
43
|
+
<Button onClick={exportToExcel}>Excel</Button>
|
|
44
|
+
</Popup.Content>
|
|
45
|
+
</Popup>
|
|
46
|
+
|
|
47
|
+
<Modal onClose={() => setOpen(false)} open={open}>
|
|
48
|
+
<Modal.Content>
|
|
49
|
+
Permissions are required to download the workbook.
|
|
50
|
+
</Modal.Content>
|
|
51
|
+
|
|
52
|
+
<Modal.Actions>
|
|
53
|
+
<Button primary onClick={() => setOpen(false)}>
|
|
54
|
+
OK
|
|
55
|
+
</Button>
|
|
56
|
+
</Modal.Actions>
|
|
57
|
+
</Modal>
|
|
58
|
+
</>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default TableauDownload;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Modal, Button } from 'semantic-ui-react';
|
|
3
|
+
import { Icon } from '@plone/volto/components';
|
|
4
|
+
import { useHistory, useLocation } from 'react-router-dom';
|
|
5
|
+
import fullscreenSVG from '@plone/volto/icons/fullscreen.svg';
|
|
6
|
+
|
|
7
|
+
import config from '@plone/volto/registry';
|
|
8
|
+
|
|
9
|
+
const TableauFullscreen = (props) => {
|
|
10
|
+
const tableau_url = props.data.url;
|
|
11
|
+
const modalHash = props?.item.getId + '_preview';
|
|
12
|
+
const [open, setOpen] = React.useState(false);
|
|
13
|
+
const history = useHistory();
|
|
14
|
+
const location = useLocation();
|
|
15
|
+
const {
|
|
16
|
+
blocks: { blocksConfig },
|
|
17
|
+
} = config;
|
|
18
|
+
const TableauBlockView = blocksConfig.tableau_block.view;
|
|
19
|
+
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
if (location.hash.includes(modalHash)) {
|
|
22
|
+
setOpen(true);
|
|
23
|
+
} else {
|
|
24
|
+
setOpen(false);
|
|
25
|
+
}
|
|
26
|
+
}, [location, modalHash]);
|
|
27
|
+
|
|
28
|
+
const closeModal = () => {
|
|
29
|
+
history.push({
|
|
30
|
+
hash: '',
|
|
31
|
+
});
|
|
32
|
+
setOpen(false);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<div className="toolbar-button-wrapper">
|
|
38
|
+
<Button
|
|
39
|
+
className="toolbar-button"
|
|
40
|
+
title="Full Screen"
|
|
41
|
+
onClick={() => {
|
|
42
|
+
setOpen(true);
|
|
43
|
+
if (props.item) {
|
|
44
|
+
history.push({
|
|
45
|
+
hash: props.item.getId + '_preview',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Icon name={fullscreenSVG} size="23px" />
|
|
51
|
+
</Button>
|
|
52
|
+
<span className="btn-text">Enlarge</span>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<Modal
|
|
56
|
+
className="tableau-fullscreen"
|
|
57
|
+
onClose={closeModal}
|
|
58
|
+
onOpen={() => setOpen(true)}
|
|
59
|
+
open={open}
|
|
60
|
+
>
|
|
61
|
+
<Modal.Content>
|
|
62
|
+
<TableauBlockView
|
|
63
|
+
{...props}
|
|
64
|
+
data={{ url: tableau_url, hideToolbar: true }}
|
|
65
|
+
></TableauBlockView>
|
|
66
|
+
</Modal.Content>
|
|
67
|
+
|
|
68
|
+
<Modal.Actions>
|
|
69
|
+
<Button primary onClick={closeModal}>
|
|
70
|
+
Close
|
|
71
|
+
</Button>
|
|
72
|
+
</Modal.Actions>
|
|
73
|
+
</Modal>
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default TableauFullscreen;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Popup, Tab, Button, Menu, Input } from 'semantic-ui-react';
|
|
3
|
+
import { Icon } from '@plone/volto/components';
|
|
4
|
+
import useCopyToClipboard from '../downloadHelpers/downloadHelpers';
|
|
5
|
+
|
|
6
|
+
import shareSVG from '@plone/volto/icons/share.svg';
|
|
7
|
+
import linkSVG from '@plone/volto/icons/link.svg';
|
|
8
|
+
|
|
9
|
+
import cx from 'classnames';
|
|
10
|
+
|
|
11
|
+
const TableauShare = (props) => {
|
|
12
|
+
const tableau_url = props.data.url;
|
|
13
|
+
|
|
14
|
+
const CopyUrlButton = ({ url, buttonText }) => {
|
|
15
|
+
const [copyUrlStatus, copyUrl] = useCopyToClipboard(url);
|
|
16
|
+
|
|
17
|
+
if (copyUrlStatus === 'copied') {
|
|
18
|
+
buttonText = 'Copied!';
|
|
19
|
+
} else if (copyUrlStatus === 'failed') {
|
|
20
|
+
buttonText = 'Copy failed. Please try again.';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Button
|
|
25
|
+
primary
|
|
26
|
+
onClick={copyUrl}
|
|
27
|
+
className={cx('copy-button', {
|
|
28
|
+
'green-button': copyUrlStatus === 'copied',
|
|
29
|
+
})}
|
|
30
|
+
>
|
|
31
|
+
{buttonText}
|
|
32
|
+
</Button>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const panes = [
|
|
37
|
+
{
|
|
38
|
+
menuItem: (
|
|
39
|
+
<Menu.Item key="location">
|
|
40
|
+
<span className="nav-dot">
|
|
41
|
+
<Icon name={linkSVG} size="24px" />
|
|
42
|
+
</span>
|
|
43
|
+
<span className="nav-dot-title">URL</span>
|
|
44
|
+
</Menu.Item>
|
|
45
|
+
),
|
|
46
|
+
render: () => (
|
|
47
|
+
<Tab.Pane>
|
|
48
|
+
<Input defaultValue={tableau_url} />
|
|
49
|
+
<CopyUrlButton url={tableau_url} buttonText="Copy sharing URL" />
|
|
50
|
+
</Tab.Pane>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Popup
|
|
57
|
+
basic
|
|
58
|
+
className="tableau-share-dialog"
|
|
59
|
+
position="top center"
|
|
60
|
+
on="click"
|
|
61
|
+
trigger={
|
|
62
|
+
<div className="toolbar-button-wrapper">
|
|
63
|
+
<Button className="toolbar-button" title="Share">
|
|
64
|
+
<Icon name={shareSVG} size="26px" />
|
|
65
|
+
</Button>
|
|
66
|
+
<span className="btn-text">Share</span>
|
|
67
|
+
</div>
|
|
68
|
+
}
|
|
69
|
+
>
|
|
70
|
+
<Popup.Header>Share Visualization</Popup.Header>
|
|
71
|
+
<Popup.Content>
|
|
72
|
+
<Tab
|
|
73
|
+
menu={{ secondary: true, pointing: true, fluid: true }}
|
|
74
|
+
panes={panes}
|
|
75
|
+
/>
|
|
76
|
+
</Popup.Content>
|
|
77
|
+
</Popup>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default TableauShare;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
.dashboard-wrapper {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
flex-wrap: wrap;
|
|
5
|
+
background-color: #f5f5f5;
|
|
6
|
+
|
|
7
|
+
.tableau-block {
|
|
8
|
+
position: relative;
|
|
9
|
+
display: flex;
|
|
10
|
+
flex: 1 1;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
padding: 1em 0.5em;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.toolbar-button {
|
|
16
|
+
position: relative;
|
|
17
|
+
width: 32px;
|
|
18
|
+
height: 32px;
|
|
19
|
+
padding: 2px !important;
|
|
20
|
+
background-color: #ff421b !important;
|
|
21
|
+
border-radius: 3px !important;
|
|
22
|
+
color: white !important;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
opacity: 0.6;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
svg {
|
|
29
|
+
position: absolute;
|
|
30
|
+
top: 50%;
|
|
31
|
+
left: 50%;
|
|
32
|
+
margin: 0 !important;
|
|
33
|
+
fill: #fff !important;
|
|
34
|
+
-webkit-transform: translate(-50%, -50%);
|
|
35
|
+
transform: translate(-50%, -50%);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.tableau-download-dialog {
|
|
41
|
+
max-width: 282px !important;
|
|
42
|
+
|
|
43
|
+
.header {
|
|
44
|
+
padding: 0.5em 0 !important;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
button {
|
|
48
|
+
display: block;
|
|
49
|
+
width: 100%;
|
|
50
|
+
margin: 5px 0 !important;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.tableau-share-dialog {
|
|
55
|
+
min-width: 400px !important;
|
|
56
|
+
|
|
57
|
+
.ui.secondary.pointing.menu .item {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
padding: 10px 25px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.nav-dot-title {
|
|
64
|
+
margin-top: 5px;
|
|
65
|
+
font-size: 14px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ui.secondary.pointing.menu .active.item {
|
|
69
|
+
border-color: #09b;
|
|
70
|
+
|
|
71
|
+
.nav-dot-title {
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.nav-dot {
|
|
76
|
+
opacity: 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.nav-dot {
|
|
81
|
+
display: flex;
|
|
82
|
+
width: 37px;
|
|
83
|
+
height: 37px;
|
|
84
|
+
align-items: center;
|
|
85
|
+
justify-content: center;
|
|
86
|
+
margin: 8px auto;
|
|
87
|
+
background-color: #09b;
|
|
88
|
+
border-radius: 50%;
|
|
89
|
+
opacity: 0.6;
|
|
90
|
+
|
|
91
|
+
svg {
|
|
92
|
+
fill: #fff !important;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.ui.secondary.pointing.menu .active.item:hover {
|
|
97
|
+
border-color: #09b;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.ui.tab {
|
|
101
|
+
.ui.input {
|
|
102
|
+
display: block;
|
|
103
|
+
margin-bottom: 1em;
|
|
104
|
+
|
|
105
|
+
input {
|
|
106
|
+
width: 100%;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
textarea {
|
|
111
|
+
display: block;
|
|
112
|
+
width: 100%;
|
|
113
|
+
height: 100px;
|
|
114
|
+
padding: 6px 12px;
|
|
115
|
+
border: 1px solid grey;
|
|
116
|
+
margin-bottom: 1.5em;
|
|
117
|
+
color: #696969;
|
|
118
|
+
font-family: 'Lucida Console', Monaco, monospace;
|
|
119
|
+
font-size: 13px;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.copy-button {
|
|
125
|
+
margin-top: 1rem !important;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.copy-button.green-button {
|
|
129
|
+
background-color: #269b65 !important;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.toolbar-button-wrapper {
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
text-align: center;
|
|
136
|
+
|
|
137
|
+
.btn-text {
|
|
138
|
+
font-family: Roboto, Helvetica Neue, Arial, Helvetica, sans-serif;
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
font-weight: 400;
|
|
141
|
+
line-height: 20px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.ui.button.toolbar-button {
|
|
145
|
+
margin: 0 7px !important;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.tableau-icons {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: row;
|
|
152
|
+
}
|
package/src/Sources/Sources.jsx
CHANGED
|
@@ -28,17 +28,19 @@ const SourcesWidget = ({ sources }) => {
|
|
|
28
28
|
<ul>
|
|
29
29
|
{sources &&
|
|
30
30
|
sources.data &&
|
|
31
|
-
sources.data.map((param, i) =>
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
sources.data.map((param, i) =>
|
|
32
|
+
param.link ? (
|
|
33
|
+
<li key={i} className="embed-source-param">
|
|
34
|
+
<UniversalLink
|
|
35
|
+
className="embed-sources-param-title"
|
|
36
|
+
href={param.link}
|
|
37
|
+
>
|
|
38
|
+
{param.title}
|
|
39
|
+
</UniversalLink>
|
|
40
|
+
, {param.organisation}
|
|
41
|
+
</li>
|
|
42
|
+
) : null,
|
|
43
|
+
)}
|
|
42
44
|
</ul>
|
|
43
45
|
)}
|
|
44
46
|
</div>
|
package/src/Tableau/View.jsx
CHANGED
|
@@ -6,6 +6,9 @@ import { Toast } from '@plone/volto/components';
|
|
|
6
6
|
import { setTableauApi } from '@eeacms/volto-tableau/actions';
|
|
7
7
|
import cx from 'classnames';
|
|
8
8
|
import { loadTableauScript } from '../helpers';
|
|
9
|
+
import TableauDownload from '../DownloadExtras/TableauDownload';
|
|
10
|
+
import TableauShare from '../DownloadExtras/TableauShare';
|
|
11
|
+
import '../DownloadExtras/style.less';
|
|
9
12
|
|
|
10
13
|
const Tableau = (props) => {
|
|
11
14
|
const ref = React.useRef(null);
|
|
@@ -212,23 +215,29 @@ const Tableau = (props) => {
|
|
|
212
215
|
''
|
|
213
216
|
) : (
|
|
214
217
|
<div className="tableau-loader">
|
|
215
|
-
<span>
|
|
216
|
-
{mode === 'edit'
|
|
217
|
-
? 'Loading...'
|
|
218
|
-
: `Loading Tableau v${version}`}
|
|
219
|
-
</span>
|
|
218
|
+
<span>Loading...</span>
|
|
220
219
|
</div>
|
|
221
220
|
)}
|
|
222
221
|
</>
|
|
223
222
|
) : (
|
|
224
223
|
<div>No data present in that visualization.</div>
|
|
225
224
|
)}
|
|
226
|
-
<div
|
|
227
|
-
className=
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
225
|
+
<div className="dashboard-wrapper">
|
|
226
|
+
<div className="tableau-block">
|
|
227
|
+
{viz ? (
|
|
228
|
+
<div className="tableau-icons">
|
|
229
|
+
<TableauDownload {...props} viz={viz} />
|
|
230
|
+
<TableauShare {...props} viz={viz} data={{ url: url }} />
|
|
231
|
+
</div>
|
|
232
|
+
) : null}
|
|
233
|
+
<div
|
|
234
|
+
className={cx('tableau', version, {
|
|
235
|
+
'tableau-scale': autoScale,
|
|
236
|
+
})}
|
|
237
|
+
ref={ref}
|
|
238
|
+
/>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
232
241
|
</div>
|
|
233
242
|
</div>
|
|
234
243
|
);
|
|
@@ -34,8 +34,6 @@ const View = (props) => {
|
|
|
34
34
|
description = null,
|
|
35
35
|
autoScale = false,
|
|
36
36
|
} = data;
|
|
37
|
-
const version =
|
|
38
|
-
props.data.version || config.settings.tableauVersion || '2.8.0';
|
|
39
37
|
const device = getDevice(config, screen.page?.width || Infinity);
|
|
40
38
|
const breakpointUrl = breakpointUrls.filter(
|
|
41
39
|
(breakpoint) => breakpoint.device === device,
|
|
@@ -67,33 +65,33 @@ const View = (props) => {
|
|
|
67
65
|
|
|
68
66
|
return mounted ? (
|
|
69
67
|
<div className="tableau-block">
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<h3 className="tableau-version">== Tableau
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
</
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)}
|
|
68
|
+
<div className="tableau-info">
|
|
69
|
+
{loaded && url && props.mode === 'edit' ? (
|
|
70
|
+
<h3 className="tableau-version">== Tableau ==</h3>
|
|
71
|
+
) : null}
|
|
72
|
+
{!url ? <p className="tableau-error">URL required</p> : ''}
|
|
73
|
+
{error ? <p className="tableau-error">{error}</p> : ''}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
79
76
|
{loaded && title ? <h3 className="tableau-title">{title}</h3> : ''}
|
|
80
77
|
{loaded && description ? (
|
|
81
78
|
<p className="tableau-description">{description}</p>
|
|
82
79
|
) : (
|
|
83
80
|
''
|
|
84
81
|
)}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
{url ? (
|
|
83
|
+
<Tableau
|
|
84
|
+
{...props}
|
|
85
|
+
canUpdateUrl={!breakpointUrl}
|
|
86
|
+
extraFilters={extraFilters}
|
|
87
|
+
extraOptions={{ device: autoScale ? 'desktop' : device }}
|
|
88
|
+
error={error}
|
|
89
|
+
loaded={loaded}
|
|
90
|
+
setError={setError}
|
|
91
|
+
setLoaded={setLoaded}
|
|
92
|
+
url={url}
|
|
93
|
+
/>
|
|
94
|
+
) : null}
|
|
97
95
|
</div>
|
|
98
96
|
) : (
|
|
99
97
|
''
|
|
@@ -46,7 +46,7 @@ export default (config) => ({
|
|
|
46
46
|
{
|
|
47
47
|
id: 'default',
|
|
48
48
|
title: 'Default',
|
|
49
|
-
fields: ['
|
|
49
|
+
fields: ['url', 'title', 'description'],
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
id: 'options',
|
|
@@ -66,24 +66,6 @@ export default (config) => ({
|
|
|
66
66
|
},
|
|
67
67
|
],
|
|
68
68
|
properties: {
|
|
69
|
-
version: {
|
|
70
|
-
title: 'Version',
|
|
71
|
-
type: 'array',
|
|
72
|
-
choices: [
|
|
73
|
-
...[
|
|
74
|
-
'2.8.0',
|
|
75
|
-
'2.7.0',
|
|
76
|
-
'2.6.0',
|
|
77
|
-
'2.5.0',
|
|
78
|
-
'2.4.0',
|
|
79
|
-
'2.3.0',
|
|
80
|
-
'2.2.2',
|
|
81
|
-
'2.1.2',
|
|
82
|
-
'2.0.3',
|
|
83
|
-
].map((version) => [version, `tableau-${version}`]),
|
|
84
|
-
],
|
|
85
|
-
default: config.settings.tableauVersion || '2.8.0',
|
|
86
|
-
},
|
|
87
69
|
url: {
|
|
88
70
|
title: 'Url',
|
|
89
71
|
widget: 'textarea',
|
|
@@ -138,5 +120,5 @@ export default (config) => ({
|
|
|
138
120
|
description: 'Set different vizualization for specific breakpoint',
|
|
139
121
|
},
|
|
140
122
|
},
|
|
141
|
-
required: ['
|
|
123
|
+
required: ['url'],
|
|
142
124
|
});
|
|
@@ -23,19 +23,18 @@ const VisualizationView = (props) => {
|
|
|
23
23
|
};
|
|
24
24
|
return (
|
|
25
25
|
<div>
|
|
26
|
-
{!tableau_visualization_data
|
|
26
|
+
{!tableau_visualization_data?.general?.url || tableauError ? (
|
|
27
27
|
<TableauNotDisplayed />
|
|
28
28
|
) : (
|
|
29
|
-
|
|
29
|
+
<TableauView
|
|
30
|
+
setTableauError={setTableauError}
|
|
31
|
+
data={{
|
|
32
|
+
...tableau_visualization_data.general,
|
|
33
|
+
...tableau_visualization_data.options,
|
|
34
|
+
...tableau_visualization_data.extraOptions,
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
30
37
|
)}
|
|
31
|
-
<TableauView
|
|
32
|
-
setTableauError={setTableauError}
|
|
33
|
-
data={{
|
|
34
|
-
...tableau_visualization_data.general,
|
|
35
|
-
...tableau_visualization_data.options,
|
|
36
|
-
...tableau_visualization_data.extraOptions,
|
|
37
|
-
}}
|
|
38
|
-
/>
|
|
39
38
|
</div>
|
|
40
39
|
);
|
|
41
40
|
};
|
|
@@ -53,14 +53,6 @@ const VisualizationWidget = (props) => {
|
|
|
53
53
|
let schema = Schema(config);
|
|
54
54
|
|
|
55
55
|
React.useEffect(() => {
|
|
56
|
-
if (!intValue?.general || !intValue?.general?.version) {
|
|
57
|
-
setIntValue({
|
|
58
|
-
...intValue,
|
|
59
|
-
general: {
|
|
60
|
-
version: '2.8.0',
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
56
|
if (!intValue?.options) {
|
|
65
57
|
setIntValue({
|
|
66
58
|
...intValue,
|
|
@@ -73,7 +65,7 @@ const VisualizationWidget = (props) => {
|
|
|
73
65
|
});
|
|
74
66
|
}
|
|
75
67
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
76
|
-
}, [
|
|
68
|
+
}, []);
|
|
77
69
|
|
|
78
70
|
return (
|
|
79
71
|
<FormFieldWrapper {...props}>
|
package/src/Widgets/schema.js
CHANGED
|
@@ -1,41 +1,21 @@
|
|
|
1
|
-
const generalSchema =
|
|
2
|
-
|
|
3
|
-
title: 'General',
|
|
1
|
+
const generalSchema = {
|
|
2
|
+
title: 'General',
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
fieldsets: [
|
|
5
|
+
{
|
|
6
|
+
id: 'general',
|
|
7
|
+
title: 'General',
|
|
8
|
+
fields: ['url'],
|
|
9
|
+
},
|
|
10
|
+
],
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
choices: [
|
|
18
|
-
...[
|
|
19
|
-
'2.8.0',
|
|
20
|
-
'2.7.0',
|
|
21
|
-
'2.6.0',
|
|
22
|
-
'2.5.0',
|
|
23
|
-
'2.4.0',
|
|
24
|
-
'2.3.0',
|
|
25
|
-
'2.2.2',
|
|
26
|
-
'2.1.2',
|
|
27
|
-
'2.0.3',
|
|
28
|
-
].map((version) => [version, `tableau-${version}`]),
|
|
29
|
-
],
|
|
30
|
-
default: config.settings.tableauVersion || '2.8.0',
|
|
31
|
-
},
|
|
32
|
-
url: {
|
|
33
|
-
title: 'Url',
|
|
34
|
-
type: 'textarea',
|
|
35
|
-
},
|
|
12
|
+
properties: {
|
|
13
|
+
url: {
|
|
14
|
+
title: 'Url',
|
|
15
|
+
type: 'textarea',
|
|
36
16
|
},
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
},
|
|
18
|
+
required: ['url'],
|
|
39
19
|
};
|
|
40
20
|
|
|
41
21
|
const optionsSchema = {
|
|
@@ -92,7 +72,11 @@ const optionsSchema = {
|
|
|
92
72
|
const urlParametersSchema = {
|
|
93
73
|
title: 'Parameter',
|
|
94
74
|
fieldsets: [
|
|
95
|
-
{
|
|
75
|
+
{
|
|
76
|
+
id: 'default',
|
|
77
|
+
title: 'Default',
|
|
78
|
+
fields: ['field', 'urlParam'],
|
|
79
|
+
},
|
|
96
80
|
],
|
|
97
81
|
properties: {
|
|
98
82
|
field: {
|
|
@@ -139,7 +123,7 @@ const extraOptionsSchema = (config) => {
|
|
|
139
123
|
{
|
|
140
124
|
id: 'default',
|
|
141
125
|
title: 'Extra Options Data',
|
|
142
|
-
fields: ['urlParameters', 'breakpointUrls'],
|
|
126
|
+
fields: ['urlParameters', 'availableFields', 'breakpointUrls'],
|
|
143
127
|
},
|
|
144
128
|
],
|
|
145
129
|
|
|
@@ -150,6 +134,10 @@ const extraOptionsSchema = (config) => {
|
|
|
150
134
|
schema: urlParametersSchema,
|
|
151
135
|
description: 'Set a list of url parameters to filter the tableau',
|
|
152
136
|
},
|
|
137
|
+
availableFields: {
|
|
138
|
+
title: 'Available Fields:',
|
|
139
|
+
widget: 'url_params_widget',
|
|
140
|
+
},
|
|
153
141
|
breakpointUrls: {
|
|
154
142
|
title: 'Breakpoint urls',
|
|
155
143
|
widget: 'object_list',
|
|
@@ -178,7 +166,7 @@ export default (config) => {
|
|
|
178
166
|
schemas: [
|
|
179
167
|
{
|
|
180
168
|
id: 'general',
|
|
181
|
-
schema: generalSchema
|
|
169
|
+
schema: generalSchema,
|
|
182
170
|
},
|
|
183
171
|
{
|
|
184
172
|
id: 'options',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const useCopyToClipboard = (text) => {
|
|
4
|
+
const [copyStatus, setCopyStatus] = React.useState('inactive');
|
|
5
|
+
const copy = React.useCallback(() => {
|
|
6
|
+
navigator.clipboard.writeText(text).then(
|
|
7
|
+
() => setCopyStatus('copied'),
|
|
8
|
+
() => setCopyStatus('failed'),
|
|
9
|
+
);
|
|
10
|
+
}, [text]);
|
|
11
|
+
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
if (copyStatus === 'inactive') {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const timeout = setTimeout(() => setCopyStatus('inactive'), 3000);
|
|
18
|
+
|
|
19
|
+
return () => clearTimeout(timeout);
|
|
20
|
+
}, [copyStatus]);
|
|
21
|
+
|
|
22
|
+
return [copyStatus, copy];
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default useCopyToClipboard;
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import EmbedTableauView from './Blocks/EmbedEEATableauBlock/View';
|
|
|
5
5
|
import EmbedTableauEdit from './Blocks/EmbedEEATableauBlock/Edit';
|
|
6
6
|
import { VisualizationView } from './Views';
|
|
7
7
|
import { VisualizationWidget } from './Widgets';
|
|
8
|
+
import UrlParamsWidget from './CustomWidgets/UrlParamsWidget';
|
|
8
9
|
|
|
9
10
|
import tableauStore from './store';
|
|
10
11
|
|
|
@@ -72,6 +73,7 @@ const applyConfig = (config) => {
|
|
|
72
73
|
|
|
73
74
|
config.views.contentTypesViews.tableau_visualization = VisualizationView;
|
|
74
75
|
config.widgets.id.tableau_visualization_data = VisualizationWidget;
|
|
76
|
+
config.widgets.widget.url_params_widget = UrlParamsWidget;
|
|
75
77
|
|
|
76
78
|
return config;
|
|
77
79
|
};
|
package/src/less/tableau.less
CHANGED
|
@@ -108,4 +108,20 @@
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
.
|
|
111
|
+
.availableFieldsContainer {
|
|
112
|
+
padding: 0 5px 5px 0;
|
|
113
|
+
border-bottom: 1px solid #d9d9d9;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.availableFieldsTitle {
|
|
117
|
+
color: darkgray;
|
|
118
|
+
font-size: 13px;
|
|
119
|
+
font-weight: bold;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.availableFields {
|
|
123
|
+
padding: 0 5px;
|
|
124
|
+
font-size: 12px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.loadAddonVariables();
|