@eeacms/volto-tableau 3.0.4 → 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 +10 -0
- package/README.md +21 -0
- package/package.json +1 -1
- package/src/Blocks/EmbedEEATableauBlock/View.jsx +8 -22
- package/src/Blocks/EmbedEEATableauBlock/schema.js +47 -6
- package/src/ConnectedTableau/ConnectedTableau.jsx +7 -1
- 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 +21 -12
- package/src/TableauBlock/View.jsx +3 -12
- package/src/TableauBlock/schema.js +2 -20
- package/src/Views/VisualizationView.jsx +1 -5
- package/src/Widgets/VisualizationWidget.jsx +2 -17
- package/src/Widgets/schema.js +16 -36
- package/src/downloadHelpers/downloadHelpers.js +25 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ 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)]
|
|
7
17
|
### [3.0.4](https://github.com/eea/volto-tableau/compare/3.0.3...3.0.4) - 18 January 2023
|
|
8
18
|
|
|
9
19
|
#### :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,26 +36,14 @@ const View = (props) => {
|
|
|
36
36
|
<PrivacyProtection data={data} {...props}>
|
|
37
37
|
{data?.vis_url ? (
|
|
38
38
|
<>
|
|
39
|
-
{tableau_visualization?.general?.url
|
|
40
|
-
tableau_visualization?.general?.version ? (
|
|
39
|
+
{tableau_visualization?.general?.url ? (
|
|
41
40
|
<>
|
|
42
|
-
<
|
|
43
|
-
{props.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
</h3>
|
|
49
|
-
</div>
|
|
50
|
-
) : (
|
|
51
|
-
''
|
|
52
|
-
)}
|
|
53
|
-
<ConnectedTableau
|
|
54
|
-
{...props.tableau_visualization}
|
|
55
|
-
id={props.id}
|
|
56
|
-
{...props}
|
|
57
|
-
/>
|
|
58
|
-
</div>
|
|
41
|
+
<ConnectedTableau
|
|
42
|
+
{...props.tableau_visualization}
|
|
43
|
+
id={props.id}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
|
|
59
47
|
{show_sources &&
|
|
60
48
|
data_provenance &&
|
|
61
49
|
data_provenance?.data?.data_provenance &&
|
|
@@ -69,9 +57,7 @@ const View = (props) => {
|
|
|
69
57
|
</>
|
|
70
58
|
) : !tableau_visualization?.general?.url ? (
|
|
71
59
|
<div>Url is not set in the visualization</div>
|
|
72
|
-
) :
|
|
73
|
-
<div>Version is not set in the visualization</div>
|
|
74
|
-
)}
|
|
60
|
+
) : null}
|
|
75
61
|
</>
|
|
76
62
|
) : (
|
|
77
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,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);
|
|
@@ -23,7 +26,7 @@ const Tableau = (props) => {
|
|
|
23
26
|
screen = {},
|
|
24
27
|
setError = () => {},
|
|
25
28
|
setLoaded = () => {},
|
|
26
|
-
version = '',
|
|
29
|
+
version = '2.8.0',
|
|
27
30
|
} = props;
|
|
28
31
|
const {
|
|
29
32
|
autoScale = false,
|
|
@@ -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,15 +34,12 @@ const View = (props) => {
|
|
|
34
34
|
description = null,
|
|
35
35
|
autoScale = false,
|
|
36
36
|
} = data;
|
|
37
|
-
const version = props.data.version || config.settings.tableauVersion;
|
|
38
37
|
const device = getDevice(config, screen.page?.width || Infinity);
|
|
39
38
|
const breakpointUrl = breakpointUrls.filter(
|
|
40
39
|
(breakpoint) => breakpoint.device === device,
|
|
41
40
|
)[0]?.url;
|
|
42
41
|
const url = breakpointUrl || data.url;
|
|
43
42
|
|
|
44
|
-
const displayCondition = url && version;
|
|
45
|
-
|
|
46
43
|
React.useEffect(() => {
|
|
47
44
|
setMounted(true);
|
|
48
45
|
/* eslint-disable-next-line */
|
|
@@ -69,15 +66,10 @@ const View = (props) => {
|
|
|
69
66
|
return mounted ? (
|
|
70
67
|
<div className="tableau-block">
|
|
71
68
|
<div className="tableau-info">
|
|
72
|
-
{
|
|
73
|
-
<h3 className="tableau-version">== Tableau
|
|
69
|
+
{loaded && url && props.mode === 'edit' ? (
|
|
70
|
+
<h3 className="tableau-version">== Tableau ==</h3>
|
|
74
71
|
) : null}
|
|
75
72
|
{!url ? <p className="tableau-error">URL required</p> : ''}
|
|
76
|
-
{url && !version ? (
|
|
77
|
-
<p className="tableau-error">Version required</p>
|
|
78
|
-
) : (
|
|
79
|
-
''
|
|
80
|
-
)}
|
|
81
73
|
{error ? <p className="tableau-error">{error}</p> : ''}
|
|
82
74
|
</div>
|
|
83
75
|
|
|
@@ -87,7 +79,7 @@ const View = (props) => {
|
|
|
87
79
|
) : (
|
|
88
80
|
''
|
|
89
81
|
)}
|
|
90
|
-
{
|
|
82
|
+
{url ? (
|
|
91
83
|
<Tableau
|
|
92
84
|
{...props}
|
|
93
85
|
canUpdateUrl={!breakpointUrl}
|
|
@@ -97,7 +89,6 @@ const View = (props) => {
|
|
|
97
89
|
loaded={loaded}
|
|
98
90
|
setError={setError}
|
|
99
91
|
setLoaded={setLoaded}
|
|
100
|
-
version={version}
|
|
101
92
|
url={url}
|
|
102
93
|
/>
|
|
103
94
|
) : null}
|
|
@@ -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,
|
|
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
|
});
|
|
@@ -12,8 +12,6 @@ const VisualizationView = (props) => {
|
|
|
12
12
|
<div className="tableau-info">
|
|
13
13
|
{!tableau_visualization_data.general?.url ? (
|
|
14
14
|
<p className="tableau-error">URL required</p>
|
|
15
|
-
) : !tableau_visualization_data.general?.version ? (
|
|
16
|
-
<p className="tableau-error">Version required</p>
|
|
17
15
|
) : tableauError ? (
|
|
18
16
|
<p className="tableau-error">{tableauError}</p>
|
|
19
17
|
) : (
|
|
@@ -25,9 +23,7 @@ const VisualizationView = (props) => {
|
|
|
25
23
|
};
|
|
26
24
|
return (
|
|
27
25
|
<div>
|
|
28
|
-
{!tableau_visualization_data?.general?.url ||
|
|
29
|
-
!tableau_visualization_data?.general?.version ||
|
|
30
|
-
tableauError ? (
|
|
26
|
+
{!tableau_visualization_data?.general?.url || tableauError ? (
|
|
31
27
|
<TableauNotDisplayed />
|
|
32
28
|
) : (
|
|
33
29
|
<TableauView
|
|
@@ -40,8 +40,6 @@ const VisualizationWidget = (props) => {
|
|
|
40
40
|
<div className="tableau-info">
|
|
41
41
|
{intValue && intValue.general && !intValue.general.url ? (
|
|
42
42
|
<p className="tableau-error">URL required</p>
|
|
43
|
-
) : intValue && intValue.general && !intValue.general.version ? (
|
|
44
|
-
<p className="tableau-error">Version required</p>
|
|
45
43
|
) : tableauError ? (
|
|
46
44
|
<p className="tableau-error">{tableauError}</p>
|
|
47
45
|
) : (
|
|
@@ -55,15 +53,6 @@ const VisualizationWidget = (props) => {
|
|
|
55
53
|
let schema = Schema(config);
|
|
56
54
|
|
|
57
55
|
React.useEffect(() => {
|
|
58
|
-
if (!intValue?.general || !intValue?.general?.version) {
|
|
59
|
-
setIntValue({
|
|
60
|
-
...intValue,
|
|
61
|
-
general: {
|
|
62
|
-
url: intValue?.general?.url,
|
|
63
|
-
version: '',
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
56
|
if (!intValue?.options) {
|
|
68
57
|
setIntValue({
|
|
69
58
|
...intValue,
|
|
@@ -118,9 +107,7 @@ const VisualizationWidget = (props) => {
|
|
|
118
107
|
/>
|
|
119
108
|
</Grid.Column>
|
|
120
109
|
<Grid.Column mobile={12} tablet={12} computer={7}>
|
|
121
|
-
{(intValue &&
|
|
122
|
-
intValue.general &&
|
|
123
|
-
(!intValue.general.url || !intValue.general.version)) ||
|
|
110
|
+
{(intValue && intValue.general && !intValue.general.url) ||
|
|
124
111
|
tableauError ? (
|
|
125
112
|
<TableauNotDisplayed />
|
|
126
113
|
) : (
|
|
@@ -152,9 +139,7 @@ const VisualizationWidget = (props) => {
|
|
|
152
139
|
</Modal.Actions>
|
|
153
140
|
</Modal>
|
|
154
141
|
)}
|
|
155
|
-
{(intValue &&
|
|
156
|
-
intValue.general &&
|
|
157
|
-
(!intValue.general.url || !intValue.general.version)) ||
|
|
142
|
+
{(intValue && intValue.general && !intValue.general.url) ||
|
|
158
143
|
tableauError ? (
|
|
159
144
|
<TableauNotDisplayed />
|
|
160
145
|
) : (
|
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,
|
|
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 = {
|
|
@@ -186,7 +166,7 @@ export default (config) => {
|
|
|
186
166
|
schemas: [
|
|
187
167
|
{
|
|
188
168
|
id: 'general',
|
|
189
|
-
schema: generalSchema
|
|
169
|
+
schema: generalSchema,
|
|
190
170
|
},
|
|
191
171
|
{
|
|
192
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;
|