@eeacms/volto-tableau 3.0.8 → 4.1.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +20 -7
  2. package/Jenkinsfile +2 -0
  3. package/jest-addon.config.js +2 -2
  4. package/package.json +1 -1
  5. package/src/{TableauBlock → Blocks/EmbedTableauVisualization}/Edit.jsx +7 -7
  6. package/src/Blocks/EmbedTableauVisualization/View.jsx +66 -0
  7. package/src/Blocks/EmbedTableauVisualization/index.js +30 -0
  8. package/src/Blocks/{EmbedEEATableauBlock → EmbedTableauVisualization}/schema.js +32 -13
  9. package/src/Blocks/TableauBlock/Edit.jsx +41 -0
  10. package/src/Blocks/TableauBlock/View.jsx +101 -0
  11. package/src/Blocks/TableauBlock/index.js +30 -0
  12. package/src/Blocks/TableauBlock/schema.js +224 -0
  13. package/src/Blocks/index.js +9 -0
  14. package/src/Tableau/Tableau.jsx +430 -0
  15. package/src/Tableau/helpers.js +41 -0
  16. package/src/Utils/Download/Download.jsx +72 -0
  17. package/src/Utils/JsonCodeSnippet/JsonCodeSnippet.jsx +48 -0
  18. package/src/Utils/Share/Share.jsx +21 -0
  19. package/src/Utils/Sources/Sources.jsx +66 -0
  20. package/src/Views/VisualizationView.jsx +18 -32
  21. package/src/Widgets/VisualizationWidget.jsx +92 -122
  22. package/src/Widgets/schema.js +88 -115
  23. package/src/helpers.js +15 -34
  24. package/src/hooks.js +18 -0
  25. package/src/icons/download.svg +5 -0
  26. package/src/index.js +4 -66
  27. package/src/less/tableau.less +172 -70
  28. package/src/less/tableau.variables +7 -13
  29. package/src/Blocks/EmbedEEATableauBlock/Edit.jsx +0 -56
  30. package/src/Blocks/EmbedEEATableauBlock/View.jsx +0 -74
  31. package/src/ConnectedTableau/ConnectedTableau.jsx +0 -29
  32. package/src/CustomWidgets/UrlParamsWidget.jsx +0 -29
  33. package/src/DownloadExtras/TableauDownload.jsx +0 -124
  34. package/src/DownloadExtras/TableauFullscreen.jsx +0 -78
  35. package/src/DownloadExtras/TableauShare.jsx +0 -81
  36. package/src/DownloadExtras/style.less +0 -152
  37. package/src/Sources/Sources.jsx +0 -50
  38. package/src/Sources/index.js +0 -3
  39. package/src/Sources/style.css +0 -7
  40. package/src/Tableau/View.jsx +0 -254
  41. package/src/TableauBlock/View.jsx +0 -109
  42. package/src/TableauBlock/schema.js +0 -124
  43. package/src/Widgets/style.less +0 -8
  44. package/src/actions.js +0 -9
  45. package/src/constants.js +0 -1
  46. package/src/downloadHelpers/downloadHelpers.js +0 -25
  47. package/src/middleware.js +0 -39
  48. package/src/store.js +0 -72
@@ -6,41 +6,42 @@
6
6
 
7
7
  .loadAddonVariables();
8
8
 
9
- .tableau-block {
10
- .tableau-info {
11
- h3.tableau-version {
12
- display: flex;
13
- height: 75px;
14
- align-items: center;
15
- justify-content: center;
16
- background-color: #f4f4f1;
17
- color: @tableauTitleColor;
18
- }
9
+ @keyframes spin {
10
+ 0% {
11
+ background-position: 0 0;
12
+ }
19
13
 
20
- p.tableau-error {
21
- color: @tableauErrorColor;
22
- font-weight: bold;
23
- }
14
+ 100% {
15
+ background-position: 50px 50px;
24
16
  }
17
+ }
25
18
 
26
- h3.tableau-title {
27
- margin: @tableauTitleMargin;
28
- color: @tableauTitleColor;
19
+ .tableau-wrapper {
20
+ padding: @tableauWrapperPadding;
21
+ margin: @tableauWrapperMargin;
22
+ background-color: @tableauWrapperBackground;
23
+
24
+ .tableau-debug {
25
+ margin: 0 auto 1rem auto;
26
+
27
+ .tableau-version .version {
28
+ color: @secondaryColor;
29
+ }
29
30
  }
30
31
 
31
- p.tableau-description {
32
- margin: @tableauDescriptionMargin;
33
- color: @tableauDescriptionColor;
32
+ .tableau-info {
33
+ margin: 0 auto;
34
34
  }
35
35
 
36
36
  .tableau {
37
- width: 100%;
37
+ margin-bottom: 1rem;
38
38
 
39
- &:not(.tableau-scale) {
40
- iframe {
41
- width: @tableauIframeWidth;
42
- min-width: @tableauMinWidth;
43
- }
39
+ &:not(.tableau-autoscale) {
40
+ overflow: auto;
41
+ }
42
+
43
+ &.tableau-autoscale {
44
+ overflow: hidden;
44
45
  }
45
46
 
46
47
  iframe {
@@ -51,77 +52,178 @@
51
52
  transform-origin: top left;
52
53
  }
53
54
  }
54
- }
55
-
56
- .not_displayed_tableau {
57
- width: 100%;
58
- min-width: 633px;
59
- height: 100%;
60
- min-height: 200px;
61
- background-color: #ebebeb;
62
- }
63
-
64
- .tableau-block {
65
- .tableau-info {
66
- p.tableau-error {
67
- padding: 10px;
68
- }
69
- }
70
55
 
71
56
  .tableau-loader {
72
57
  display: flex;
73
58
  overflow: hidden;
74
59
  width: 100%;
75
- height: 100%;
60
+ height: auto;
76
61
  align-items: center;
77
62
  justify-content: center;
78
- margin: auto;
79
- animation: anim 1s linear infinite;
63
+ margin: 0 auto 1rem auto;
64
+ animation: spin 1s linear infinite;
80
65
  background-image: linear-gradient(
81
66
  -45deg,
82
- #5ac5f1 25%,
83
- #96bbde 25%,
84
- #96bbde 50%,
85
- #5ac5f1 50%,
86
- #5ac5f1 75%,
87
- #96bbde 75%
67
+ @primaryColor 25%,
68
+ @secondaryColor 25%,
69
+ @secondaryColor 50%,
70
+ @primaryColor 50%,
71
+ @primaryColor 75%,
72
+ @secondaryColor 75%
88
73
  );
89
74
  background-size: 100px 100px;
90
75
  border-radius: 5px;
76
+
77
+ span {
78
+ margin: 6px auto;
79
+ color: white;
80
+ font-weight: bold;
81
+ text-align: center;
82
+ }
83
+ }
84
+
85
+ .tableau-error {
86
+ color: @errorTextColor;
87
+ }
88
+
89
+ .reload-tableau {
90
+ position: absolute;
91
+ right: 1rem;
92
+ }
93
+
94
+ .json-snippet {
95
+ .key {
96
+ color: @secondaryColor;
97
+ }
98
+ }
99
+
100
+ .important-note {
101
+ color: @secondaryColor;
102
+
103
+ .clear-filter-button {
104
+ padding: 0;
105
+ border: none;
106
+ margin: 0;
107
+ background-color: transparent;
108
+ color: inherit;
109
+ cursor: pointer;
110
+ text-decoration: underline;
111
+
112
+ &:hover {
113
+ color: @primaryColor;
114
+ }
115
+ }
91
116
  }
92
117
  }
93
118
 
94
- .tableau-loader span {
95
- margin: 6px auto;
96
- color: white;
119
+ // ========= Sources ==========
120
+
121
+ .tableau-wrapper .tableau-info {
122
+ display: flex;
123
+ align-items: center;
124
+
125
+ > * {
126
+ padding: 0 0.5rem;
127
+ border-collapse: collapse;
128
+ }
129
+
130
+ > *:first-child {
131
+ padding-left: 0;
132
+ }
133
+
134
+ > *:last-child {
135
+ padding-right: 0;
136
+ }
137
+
138
+ > *:not(:last-child) {
139
+ border-right: 2px solid @textColor;
140
+ }
141
+ }
142
+
143
+ .embed-sources-header {
144
+ cursor: pointer;
145
+ }
146
+
147
+ .tableau-download-button,
148
+ .tableau-share-button,
149
+ .tableau-sources-button {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ padding-bottom: 3px;
153
+ border: none;
154
+ background-color: transparent;
155
+ color: @textColor;
156
+ cursor: pointer;
97
157
  font-weight: bold;
98
- text-align: center;
158
+
159
+ .icon {
160
+ margin-left: 0.5rem;
161
+ }
162
+
163
+ &:hover {
164
+ color: @secondaryColor;
165
+ }
166
+
167
+ &.expanded {
168
+ padding-bottom: 0;
169
+ border-bottom: 3px solid @secondaryColor;
170
+ color: @secondaryColor;
171
+ }
99
172
  }
100
173
 
101
- @keyframes anim {
102
- 0% {
103
- background-position: 0 0;
174
+ #tableau-sources-popup,
175
+ #tableau-download-popup {
176
+ .ui.popup {
177
+ min-width: 600px;
178
+ background-color: #f9f9f9;
179
+ box-shadow: rgba(0, 0, 0, 0.15) 1.95px 1.95px 2.6px;
180
+
181
+ &::before {
182
+ background-color: #f9f9f9;
183
+ }
184
+
185
+ .sources-list > li,
186
+ .sources-list > li > * {
187
+ color: @textColor;
188
+ }
189
+
190
+ a:hover {
191
+ color: @secondaryColor;
192
+ }
104
193
  }
105
194
 
106
- 100% {
107
- background-position: 50px 50px;
195
+ .sources-list {
196
+ margin: 0;
197
+ list-style: decimal inside;
198
+ padding-inline-start: 0;
199
+ }
200
+
201
+ @media screen and (max-width: @largestMobileScreen) {
202
+ .ui.popup {
203
+ min-width: 300px;
204
+ }
108
205
  }
109
206
  }
110
207
 
111
- .availableFieldsContainer {
112
- padding: 0 5px 5px 0;
113
- border-bottom: 1px solid #d9d9d9;
208
+ #tableau-download-popup {
209
+ .ui.popup {
210
+ min-width: 460px;
211
+ text-align: center;
212
+ }
114
213
  }
115
214
 
116
- .availableFieldsTitle {
117
- color: darkgray;
118
- font-size: 13px;
119
- font-weight: bold;
215
+ @media print {
216
+ .tableau-download-container,
217
+ .tableau-sources-container,
218
+ .tableau-share-container,
219
+ #tableau-sources-popup {
220
+ display: none;
221
+ }
120
222
  }
121
223
 
122
- .availableFields {
123
- padding: 0 5px;
124
- font-size: 12px;
224
+ #tableau-editor-modal {
225
+ width: 90vw !important;
226
+ height: calc(80vh - 10em) !important;
125
227
  }
126
228
 
127
229
  .loadAddonVariables();
@@ -1,14 +1,8 @@
1
- @tableauTitleColor: #4296b2;
2
- @tableauTitleMargin: 0 0 1rem 0;
1
+ @tableauWrapperBackground : #f5f5f5;
2
+ @tableauWrapperPadding : 1rem;
3
+ @tableauWrapperMargin : 0 0 1rem 0;
3
4
 
4
- @tableauDescriptionColor: #000;
5
- @tableauDescriptionMargin: 0 0 1rem 0;
6
-
7
- @tableauErrorColor: #E9776D;
8
-
9
- @tableauIframeWidth: 100% !important;
10
- @tableauMinWidth: auto;
11
- @tableauIframePadding: 0;
12
- @tableauIframeMargin: 0 0 1rem 0;
13
- @tableauIframeBorder: none;
14
- @tableauIframeBorderRadius: 0;
5
+ @tableauIframePadding : 0;
6
+ @tableauIframeMargin : 0 auto 1rem auto;
7
+ @tableauIframeBorder : none;
8
+ @tableauIframeBorderRadius : 0;
@@ -1,56 +0,0 @@
1
- import React from 'react';
2
- import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
3
- import { SidebarPortal } from '@plone/volto/components';
4
- import { getContent } from '@plone/volto/actions';
5
- import View from './View';
6
- import Schema from './schema';
7
-
8
- import { connect } from 'react-redux';
9
- import { compose } from 'redux';
10
-
11
- const Edit = (props) => {
12
- const { block, onChangeBlock, id } = props;
13
- const data = React.useMemo(() => props.data || {}, [props.data]);
14
- const schema = React.useMemo(() => Schema(props), [props]);
15
-
16
- React.useEffect(() => {
17
- if (!Object.hasOwn(data, 'show_sources')) {
18
- onChangeBlock(block, {
19
- ...data,
20
- show_sources: true,
21
- });
22
- }
23
- }, [block, data, onChangeBlock]);
24
-
25
- return (
26
- <React.Fragment>
27
- <View {...props} data={data} id={id} mode="edit" />
28
- <SidebarPortal selected={props.selected}>
29
- <BlockDataForm
30
- block={block}
31
- title={schema.title}
32
- schema={schema}
33
- onChangeField={(id, value) => {
34
- props.onChangeBlock(block, {
35
- ...data,
36
- [id]: value,
37
- });
38
- }}
39
- formData={data}
40
- />
41
- </SidebarPortal>
42
- </React.Fragment>
43
- );
44
- };
45
-
46
- export default compose(
47
- connect(
48
- (state, props) => ({
49
- block_data: state.content.data,
50
- data_provenance: state.content.subrequests?.[props.id]?.data_provenance,
51
- }),
52
- {
53
- getContent,
54
- },
55
- ),
56
- )(React.memo(Edit));
@@ -1,74 +0,0 @@
1
- import React from 'react';
2
- import ConnectedTableau from '../../ConnectedTableau/ConnectedTableau';
3
- import { Sources } from '../../Sources';
4
- import { PrivacyProtection } from '@eeacms/volto-embed';
5
-
6
- import { getContent } from '@plone/volto/actions';
7
-
8
- import { connect } from 'react-redux';
9
- import { compose } from 'redux';
10
-
11
- const View = (props) => {
12
- const { data_provenance, tableau_visualization } = props || {};
13
- const data = React.useMemo(() => props.data || {}, [props.data]);
14
- const { vis_url = '' } = data;
15
- const show_sources = data?.show_sources ?? false;
16
-
17
- React.useEffect(() => {
18
- if (vis_url) {
19
- props.getContent(vis_url, null, props.id);
20
- }
21
- // eslint-disable-next-line react-hooks/exhaustive-deps
22
- }, [vis_url]);
23
-
24
- return (
25
- <div className="embed-container">
26
- <PrivacyProtection
27
- {...props}
28
- data={{ ...data, url: tableau_visualization?.general?.url }}
29
- >
30
- {data?.vis_url ? (
31
- <>
32
- {tableau_visualization?.general?.url ? (
33
- <>
34
- <ConnectedTableau
35
- {...props.tableau_visualization}
36
- id={props.id}
37
- {...props}
38
- />
39
-
40
- {show_sources &&
41
- data_provenance &&
42
- data_provenance?.data?.data_provenance &&
43
- tableau_visualization ? (
44
- <Sources sources={data_provenance.data.data_provenance} />
45
- ) : show_sources ? (
46
- <div>Data provenance is not set in the visualization</div>
47
- ) : (
48
- ''
49
- )}
50
- </>
51
- ) : !tableau_visualization?.general?.url ? (
52
- <div>Url is not set in the visualization</div>
53
- ) : null}
54
- </>
55
- ) : (
56
- <div>Please select a visualization from block editor.</div>
57
- )}
58
- </PrivacyProtection>
59
- </div>
60
- );
61
- };
62
-
63
- export default compose(
64
- connect(
65
- (state, props) => ({
66
- data_provenance: state.content.subrequests?.[props.id],
67
- tableau_visualization:
68
- state.content.subrequests?.[props.id]?.data?.tableau_visualization_data,
69
- }),
70
- {
71
- getContent,
72
- },
73
- ),
74
- )(React.memo(View));
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import Tableau from '@eeacms/volto-tableau/Tableau/View';
3
-
4
- const ConnectedTableau = (props) => {
5
- const [error, setError] = React.useState(null);
6
- const [loaded, setLoaded] = React.useState(null);
7
- return (
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
- )}
16
- <Tableau
17
- error={error}
18
- loaded={loaded}
19
- setError={setError}
20
- setLoaded={setLoaded}
21
- data={{ ...props?.general, ...props?.options, ...props?.extraOptions }}
22
- url={props?.general?.url}
23
- {...props}
24
- />
25
- </div>
26
- );
27
- };
28
-
29
- export default ConnectedTableau;
@@ -1,29 +0,0 @@
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;
@@ -1,124 +0,0 @@
1
- import React from 'react';
2
- import { Popup, Button, Modal, Dropdown } 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 viz = props.viz || {};
8
-
9
- const [open, setOpen] = React.useState(false);
10
- const [
11
- isSheetSelectorCsvVisible,
12
- setIsSheetSelectorCsvVisible,
13
- ] = React.useState(false);
14
- const [
15
- isSheetSelectorExcelVisible,
16
- setIsSheetSelectorExcelVisible,
17
- ] = React.useState(false);
18
- const [availableSheets, setAvailableSheets] = React.useState([]);
19
-
20
- const initializeDropdown = () => {
21
- const sheets = viz
22
- ?.getWorkbook()
23
- ?.getActiveSheet()
24
- ?.getWorksheets()
25
- .map((sheet, index) => ({
26
- key: index,
27
- text: sheet.getName(),
28
- value: sheet.getName(),
29
- }));
30
- setAvailableSheets(sheets);
31
- };
32
-
33
- const exportImage = () => {
34
- setIsSheetSelectorCsvVisible(false);
35
- setIsSheetSelectorExcelVisible(false);
36
- viz.showExportImageDialog();
37
- };
38
-
39
- const exportToCSV = () => {
40
- setIsSheetSelectorExcelVisible(false);
41
- initializeDropdown();
42
- setIsSheetSelectorCsvVisible(true);
43
- };
44
-
45
- const exportToExcel = () => {
46
- setIsSheetSelectorCsvVisible(false);
47
- initializeDropdown();
48
- setIsSheetSelectorExcelVisible(true);
49
- };
50
-
51
- const handleSheetSelectionCsvChange = (e, data) => {
52
- viz.showExportCrossTabDialog(data.value);
53
- };
54
-
55
- const handleSheetSelectionExcelChange = (e, data) => {
56
- viz.exportCrossTabToExcel(data.value);
57
- };
58
-
59
- const hideDropdowns = () => {
60
- setIsSheetSelectorCsvVisible(false);
61
- setIsSheetSelectorExcelVisible(false);
62
- };
63
-
64
- return (
65
- <>
66
- <Popup
67
- basic
68
- className="tableau-download-dialog"
69
- position="top center"
70
- on="click"
71
- trigger={
72
- <div className="toolbar-button-wrapper">
73
- <Button
74
- className="toolbar-button"
75
- title="Download"
76
- onClick={hideDropdowns}
77
- >
78
- <Icon name={downloadSVG} size="26px" />
79
- </Button>
80
- <span className="btn-text">Save</span>
81
- </div>
82
- }
83
- >
84
- <Popup.Header>Download</Popup.Header>
85
- <Popup.Content>
86
- <p>Select your file format.</p>
87
- <Button onClick={exportImage}>Image</Button>
88
- <Button onClick={exportToCSV}>CSV</Button>
89
- {isSheetSelectorCsvVisible ? (
90
- <Dropdown
91
- placeholder="Sheet"
92
- selection
93
- options={availableSheets}
94
- onChange={handleSheetSelectionCsvChange}
95
- />
96
- ) : null}
97
- <Button onClick={exportToExcel}>Excel</Button>
98
- {isSheetSelectorExcelVisible ? (
99
- <Dropdown
100
- placeholder="Sheet"
101
- selection
102
- options={availableSheets}
103
- onChange={handleSheetSelectionExcelChange}
104
- />
105
- ) : null}
106
- </Popup.Content>
107
- </Popup>
108
-
109
- <Modal onClose={() => setOpen(false)} open={open}>
110
- <Modal.Content>
111
- Permissions are required to download the workbook.
112
- </Modal.Content>
113
-
114
- <Modal.Actions>
115
- <Button primary onClick={() => setOpen(false)}>
116
- OK
117
- </Button>
118
- </Modal.Actions>
119
- </Modal>
120
- </>
121
- );
122
- };
123
-
124
- export default TableauDownload;
@@ -1,78 +0,0 @@
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;