@eeacms/volto-tableau 4.0.0 → 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.
- package/CHANGELOG.md +7 -1
- package/package.json +1 -1
- package/src/Blocks/EmbedTableauVisualization/View.jsx +8 -5
- package/src/Blocks/EmbedTableauVisualization/index.js +3 -3
- package/src/Blocks/EmbedTableauVisualization/schema.js +3 -0
- package/src/Blocks/TableauBlock/Edit.jsx +11 -2
- package/src/Blocks/TableauBlock/View.jsx +47 -44
- package/src/Blocks/TableauBlock/index.js +3 -3
- package/src/Blocks/TableauBlock/schema.js +158 -106
- package/src/Tableau/Tableau.jsx +257 -90
- package/src/Tableau/helpers.js +41 -0
- package/src/Utils/Download/Download.jsx +4 -4
- package/src/Utils/JsonCodeSnippet/JsonCodeSnippet.jsx +48 -0
- package/src/Views/VisualizationView.jsx +10 -4
- package/src/Widgets/VisualizationWidget.jsx +38 -4
- package/src/Widgets/schema.js +115 -73
- package/src/less/tableau.less +46 -3
- package/src/less/tableau.variables +2 -1
package/src/Tableau/Tableau.jsx
CHANGED
|
@@ -1,57 +1,89 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
useEffect,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
useMemo,
|
|
7
|
+
useCallback,
|
|
8
|
+
forwardRef,
|
|
9
|
+
} from 'react';
|
|
2
10
|
import { connect } from 'react-redux';
|
|
3
11
|
import { toast } from 'react-toastify';
|
|
4
12
|
import isEqual from 'lodash/isEqual';
|
|
5
|
-
import
|
|
13
|
+
import isUndefined from 'lodash/isUndefined';
|
|
6
14
|
import cx from 'classnames';
|
|
15
|
+
import { Button } from 'semantic-ui-react';
|
|
16
|
+
import { Toast, Icon } from '@plone/volto/components';
|
|
7
17
|
import { useTableau } from '@eeacms/volto-tableau/hooks';
|
|
18
|
+
import JsonCodeSnippet from '@eeacms/volto-tableau/Utils/JsonCodeSnippet/JsonCodeSnippet';
|
|
8
19
|
import Sources from '@eeacms/volto-tableau/Utils/Sources/Sources';
|
|
9
20
|
import Download from '@eeacms/volto-tableau/Utils/Download/Download';
|
|
10
21
|
import Share from '@eeacms/volto-tableau/Utils/Share/Share';
|
|
22
|
+
import { getSheetnames, getActiveSheetname, getDevice } from './helpers';
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
import resetSVG from '@plone/volto/icons/reset.svg';
|
|
25
|
+
|
|
26
|
+
const TableauDebug = ({ mode, data, vizState, url, version, clearData }) => {
|
|
13
27
|
const { loaded, error } = vizState;
|
|
28
|
+
const { filters = {}, parameters = {} } = data;
|
|
14
29
|
|
|
15
30
|
const showTableauInfo = mode === 'edit' && (!url || (loaded && url) || error);
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
{vizState.loaded && url
|
|
32
|
+
if (!showTableauInfo) return null;
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="tableau-debug">
|
|
36
|
+
{!url && !vizState.error && <p className="tableau-error">URL required</p>}
|
|
37
|
+
{vizState.error && <p className="tableau-error">{vizState.error}</p>}
|
|
38
|
+
{vizState.loaded && url && (
|
|
24
39
|
<h3 className="tableau-version">
|
|
25
40
|
Tableau <span className="version">{version}</span>
|
|
26
41
|
</h3>
|
|
27
|
-
)
|
|
28
|
-
|
|
42
|
+
)}
|
|
43
|
+
|
|
44
|
+
{vizState.loaded && url && (
|
|
45
|
+
<>
|
|
46
|
+
<p>
|
|
47
|
+
Apply filters / parameters inside of tableau to set default static
|
|
48
|
+
filters / parameters.
|
|
49
|
+
</p>
|
|
50
|
+
<p className="important-note">
|
|
51
|
+
Click{' '}
|
|
52
|
+
<button className="clear-filter-button" onClick={clearData}>
|
|
53
|
+
here
|
|
54
|
+
</button>{' '}
|
|
55
|
+
to reset applied filters / parameters.
|
|
56
|
+
</p>
|
|
57
|
+
<code className="json-snippet">
|
|
58
|
+
<JsonCodeSnippet obj={{ filters, parameters }} />
|
|
59
|
+
</code>
|
|
60
|
+
</>
|
|
61
|
+
)}
|
|
29
62
|
</div>
|
|
30
|
-
)
|
|
63
|
+
);
|
|
31
64
|
};
|
|
32
65
|
|
|
33
|
-
const Tableau = (props) => {
|
|
34
|
-
const filters = useRef(props.data.filters || {});
|
|
66
|
+
const Tableau = forwardRef((props, ref) => {
|
|
35
67
|
const vizEl = useRef(null);
|
|
36
68
|
const viz = useRef();
|
|
37
69
|
const vizState = useRef({});
|
|
70
|
+
const dataRef = useRef(props.data || {});
|
|
71
|
+
const [initiateViz, setInitiateViz] = useState(false);
|
|
38
72
|
const [loaded, setLoaded] = useState(false);
|
|
39
73
|
const [loading, setLoading] = useState(false);
|
|
40
74
|
const [error, setError] = useState(null);
|
|
41
|
-
const [sheetSize, setSheetSize] = useState({});
|
|
42
75
|
const {
|
|
43
|
-
|
|
76
|
+
block,
|
|
44
77
|
data = {},
|
|
78
|
+
breakpoints = {},
|
|
45
79
|
extraFilters = {},
|
|
46
80
|
extraOptions = {},
|
|
47
81
|
mode = 'view',
|
|
48
82
|
screen = {},
|
|
49
|
-
version = '2.
|
|
50
|
-
with_sources,
|
|
51
|
-
with_download,
|
|
52
|
-
with_share,
|
|
83
|
+
version = '2.8.0',
|
|
53
84
|
sources,
|
|
54
|
-
|
|
85
|
+
setVizState,
|
|
86
|
+
onChangeBlock,
|
|
55
87
|
} = props;
|
|
56
88
|
const {
|
|
57
89
|
autoScale = false,
|
|
@@ -59,39 +91,121 @@ const Tableau = (props) => {
|
|
|
59
91
|
hideToolbar = false,
|
|
60
92
|
sheetname = '',
|
|
61
93
|
toolbarPosition = 'Top',
|
|
94
|
+
breakpointUrls = [],
|
|
95
|
+
with_sources,
|
|
96
|
+
with_download,
|
|
97
|
+
with_share,
|
|
62
98
|
} = data;
|
|
63
|
-
|
|
64
|
-
const
|
|
99
|
+
|
|
100
|
+
const device = useMemo(
|
|
101
|
+
() => getDevice(breakpoints, screen.page?.width || Infinity),
|
|
102
|
+
[breakpoints, screen],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const breakpointUrl = useMemo(
|
|
106
|
+
() =>
|
|
107
|
+
breakpointUrls.filter((breakpoint) => breakpoint.device === device)[0]
|
|
108
|
+
?.url,
|
|
109
|
+
[breakpointUrls, device],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const url = breakpointUrl || data.url;
|
|
65
113
|
|
|
66
114
|
// Load tableau from script tag
|
|
67
115
|
const tableau = useTableau(version);
|
|
68
116
|
|
|
69
117
|
const onFilterChange = (filter) => {
|
|
70
|
-
|
|
118
|
+
let value;
|
|
119
|
+
const filters = dataRef.current.filters;
|
|
120
|
+
const newFilters = { ...filters };
|
|
71
121
|
const fieldName = filter.getFieldName();
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
const filterType = filter.getFilterType();
|
|
123
|
+
switch (filterType) {
|
|
124
|
+
// https://community.tableau.com/s/idea/0874T000000HA8hQAG/detail
|
|
125
|
+
// Only categorical filters are allowed
|
|
126
|
+
case tableau.FilterType.CATEGORICAL:
|
|
127
|
+
const isAllSelected = filter.getIsAllSelected();
|
|
128
|
+
if (!isAllSelected) {
|
|
129
|
+
value = filter
|
|
130
|
+
.getAppliedValues()
|
|
131
|
+
.map((appliedValue) => appliedValue.value);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case tableau.FilterType.QUANTITATIVE:
|
|
135
|
+
break;
|
|
136
|
+
case tableau.FilterType.HIERARCHICAL:
|
|
137
|
+
break;
|
|
138
|
+
case tableau.FilterType.RELATIVE_DATE:
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (isUndefined(value) && !isUndefined(newFilters[fieldName])) {
|
|
144
|
+
delete newFilters[fieldName];
|
|
145
|
+
} else if (!isUndefined(value)) {
|
|
146
|
+
newFilters[fieldName] = value;
|
|
147
|
+
}
|
|
76
148
|
if (!isEqual(newFilters, filters)) {
|
|
77
|
-
|
|
78
|
-
...
|
|
149
|
+
onChangeBlock(block, {
|
|
150
|
+
...dataRef.current,
|
|
79
151
|
filters: {
|
|
80
152
|
...newFilters,
|
|
81
153
|
},
|
|
82
154
|
});
|
|
83
|
-
filters.current = { ...newFilters };
|
|
84
155
|
}
|
|
85
156
|
};
|
|
86
157
|
|
|
87
|
-
const
|
|
158
|
+
const onParameterChange = (parameter) => {
|
|
159
|
+
const parameters = dataRef.current.parameters;
|
|
160
|
+
const newParameters = { ...parameters };
|
|
161
|
+
const fieldName = parameter.getName();
|
|
162
|
+
const value = parameter.getCurrentValue()?.value;
|
|
163
|
+
if (isUndefined(value) && !isUndefined(newParameters[fieldName])) {
|
|
164
|
+
delete newParameters[fieldName];
|
|
165
|
+
} else {
|
|
166
|
+
newParameters[fieldName] = [value];
|
|
167
|
+
}
|
|
168
|
+
if (!isEqual(newParameters, parameters)) {
|
|
169
|
+
onChangeBlock(block, {
|
|
170
|
+
...dataRef.current,
|
|
171
|
+
parameters: {
|
|
172
|
+
...newParameters,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const onVizStateUpdate = useCallback((loaded, loading, error) => {
|
|
88
179
|
vizState.current = { ...vizState.current, loaded, loading, error };
|
|
89
180
|
setLoaded(loaded);
|
|
90
181
|
setLoading(loading);
|
|
91
182
|
setError(error);
|
|
92
|
-
if (
|
|
93
|
-
|
|
183
|
+
if (setVizState) {
|
|
184
|
+
setVizState({ loaded, loading, error });
|
|
185
|
+
}
|
|
186
|
+
/* eslint-disable-next-line */
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const activateDefaultSheet = useCallback(() => {
|
|
190
|
+
if (!vizState.current.loaded || !viz.current) return;
|
|
191
|
+
const workbook = viz.current.getWorkbook();
|
|
192
|
+
const sheetnames = getSheetnames(viz.current);
|
|
193
|
+
const activeSheetName = getActiveSheetname(viz.current);
|
|
194
|
+
if (sheetnames.includes(sheetname) && sheetname !== activeSheetName) {
|
|
195
|
+
workbook.activateSheetAsync(sheetname).then(() => {
|
|
196
|
+
onVizStateUpdate(true, false, null);
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
onVizStateUpdate(true, false, null);
|
|
94
200
|
}
|
|
201
|
+
}, [onVizStateUpdate, sheetname]);
|
|
202
|
+
|
|
203
|
+
const clearData = () => {
|
|
204
|
+
onChangeBlock(block, {
|
|
205
|
+
...data,
|
|
206
|
+
filters: {},
|
|
207
|
+
parameters: {},
|
|
208
|
+
});
|
|
95
209
|
};
|
|
96
210
|
|
|
97
211
|
const disposeViz = () => {
|
|
@@ -103,28 +217,34 @@ const Tableau = (props) => {
|
|
|
103
217
|
};
|
|
104
218
|
|
|
105
219
|
const initViz = () => {
|
|
106
|
-
disposeViz();
|
|
107
220
|
try {
|
|
108
221
|
onVizStateUpdate(false, true, vizState.current.error);
|
|
109
|
-
|
|
110
|
-
viz.current = new tableau.Viz(vizEl.current, url || defaultUrl, {
|
|
222
|
+
viz.current = new tableau.Viz(vizEl.current, url, {
|
|
111
223
|
hideTabs,
|
|
112
224
|
hideToolbar,
|
|
113
|
-
sheetname,
|
|
114
225
|
toolbarPosition,
|
|
226
|
+
device: !!breakpointUrl ? device : 'desktop',
|
|
115
227
|
...data.filters,
|
|
228
|
+
...data.parameters,
|
|
116
229
|
...extraFilters,
|
|
117
230
|
...extraOptions,
|
|
118
231
|
onFirstInteractive: () => {
|
|
119
|
-
onVizStateUpdate(true,
|
|
120
|
-
|
|
121
|
-
|
|
232
|
+
onVizStateUpdate(true, true, null);
|
|
233
|
+
setInitiateViz(false);
|
|
234
|
+
activateDefaultSheet();
|
|
235
|
+
if (viz.current && mode === 'edit' && !breakpointUrl) {
|
|
236
|
+
const sheetnames = getSheetnames(viz.current);
|
|
237
|
+
const activeSheetname = getActiveSheetname(viz.current);
|
|
238
|
+
const vizUrl = viz.current.getUrl();
|
|
122
239
|
const newData = {
|
|
123
|
-
|
|
124
|
-
sheetname: workbook.getActiveSheet().getName(),
|
|
240
|
+
...data,
|
|
125
241
|
};
|
|
242
|
+
newData.url = vizUrl;
|
|
243
|
+
if (!sheetname || !sheetnames.includes(sheetname)) {
|
|
244
|
+
newData.sheetname = activeSheetname;
|
|
245
|
+
}
|
|
126
246
|
if (newData.url !== url || newData.sheetname !== sheetname) {
|
|
127
|
-
|
|
247
|
+
onChangeBlock(block, {
|
|
128
248
|
...data,
|
|
129
249
|
...newData,
|
|
130
250
|
});
|
|
@@ -132,6 +252,8 @@ const Tableau = (props) => {
|
|
|
132
252
|
<Toast success title={'Tableau data updated'} content={null} />,
|
|
133
253
|
);
|
|
134
254
|
}
|
|
255
|
+
}
|
|
256
|
+
if (viz.current && mode === 'edit') {
|
|
135
257
|
// Filter change event
|
|
136
258
|
viz.current.addEventListener(
|
|
137
259
|
tableau.TableauEventName.FILTER_CHANGE,
|
|
@@ -141,17 +263,21 @@ const Tableau = (props) => {
|
|
|
141
263
|
});
|
|
142
264
|
},
|
|
143
265
|
);
|
|
266
|
+
// Parameter change event
|
|
267
|
+
viz.current.addEventListener(
|
|
268
|
+
tableau.TableauEventName.PARAMETER_VALUE_CHANGE,
|
|
269
|
+
(event) => {
|
|
270
|
+
event.getParameterAsync().then((parameter) => {
|
|
271
|
+
onParameterChange(parameter);
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
);
|
|
144
275
|
}
|
|
145
276
|
},
|
|
146
|
-
// onFirstVizSizeKnown: (e) => {
|
|
147
|
-
// if (!noSizeUpdate) {
|
|
148
|
-
// setSheetSize(e.$2.sheetSize.maxSize);
|
|
149
|
-
// }
|
|
150
|
-
// },
|
|
151
277
|
});
|
|
152
278
|
} catch (e) {
|
|
153
|
-
onVizStateUpdate(false, false, e.
|
|
154
|
-
|
|
279
|
+
onVizStateUpdate(false, false, e.get_message());
|
|
280
|
+
setInitiateViz(false);
|
|
155
281
|
}
|
|
156
282
|
};
|
|
157
283
|
|
|
@@ -177,87 +303,128 @@ const Tableau = (props) => {
|
|
|
177
303
|
};
|
|
178
304
|
|
|
179
305
|
const updateScale = () => {
|
|
180
|
-
const tableauEl = vizEl.current;
|
|
181
|
-
const tableau = tableauEl.querySelector('iframe');
|
|
306
|
+
const tableauEl = vizEl.current.querySelector('iframe');
|
|
182
307
|
const { sheetSize = {} } = viz.current.getVizSize() || {};
|
|
183
308
|
const vizWidth = sheetSize?.minSize?.width || 1;
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
309
|
+
const scale = Math.min(vizEl.current.clientWidth / vizWidth, 1);
|
|
310
|
+
tableauEl.style.transform = `scale(${scale})`;
|
|
311
|
+
window.requestAnimationFrame(() => {
|
|
312
|
+
if (vizEl.current) {
|
|
313
|
+
vizEl.current.style.height = `${scale * tableauEl.clientHeight}px`;
|
|
314
|
+
}
|
|
315
|
+
});
|
|
189
316
|
};
|
|
190
317
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
318
|
+
// Update refs
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
dataRef.current = data;
|
|
321
|
+
}, [data]);
|
|
322
|
+
|
|
323
|
+
useEffect(() => {
|
|
324
|
+
if (!tableau) return;
|
|
325
|
+
if (url) {
|
|
326
|
+
disposeViz();
|
|
327
|
+
setInitiateViz(true);
|
|
194
328
|
} else {
|
|
195
329
|
disposeViz();
|
|
196
330
|
}
|
|
331
|
+
/* eslint-disable-next-line */
|
|
332
|
+
}, [tableau, url, hideTabs, hideToolbar, toolbarPosition]);
|
|
197
333
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (initiateViz && !loaded && !loading) {
|
|
336
|
+
initViz();
|
|
337
|
+
}
|
|
201
338
|
/* eslint-disable-next-line */
|
|
202
|
-
}, [
|
|
203
|
-
hideTabs,
|
|
204
|
-
hideToolbar,
|
|
205
|
-
autoScale,
|
|
206
|
-
sheetname,
|
|
207
|
-
tableau,
|
|
208
|
-
toolbarPosition,
|
|
209
|
-
url,
|
|
210
|
-
]);
|
|
339
|
+
}, [loaded, loading, initiateViz]);
|
|
211
340
|
|
|
212
|
-
|
|
341
|
+
useEffect(() => {
|
|
213
342
|
if (vizState.current.loaded && viz.current) {
|
|
214
343
|
addExtraFilters(extraFilters);
|
|
215
344
|
}
|
|
216
345
|
/* eslint-disable-next-line */
|
|
217
346
|
}, [JSON.stringify(extraFilters)]);
|
|
218
347
|
|
|
219
|
-
|
|
348
|
+
useEffect(() => {
|
|
220
349
|
if (vizState.current.loaded && viz.current && autoScale) {
|
|
221
350
|
updateScale();
|
|
222
351
|
}
|
|
223
352
|
/* eslint-disable-next-line */
|
|
224
353
|
}, [loaded, screen?.page?.width]);
|
|
225
354
|
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
if (vizState.current.loaded && viz.current) {
|
|
357
|
+
onVizStateUpdate(true, true, null);
|
|
358
|
+
activateDefaultSheet();
|
|
359
|
+
}
|
|
360
|
+
/* eslint-disable-next-line */
|
|
361
|
+
}, [sheetname]);
|
|
362
|
+
|
|
363
|
+
useImperativeHandle(
|
|
364
|
+
ref,
|
|
365
|
+
() => {
|
|
366
|
+
if (loaded) {
|
|
367
|
+
return viz.current;
|
|
368
|
+
}
|
|
369
|
+
if (loading) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
},
|
|
374
|
+
[loaded, loading],
|
|
375
|
+
);
|
|
376
|
+
|
|
226
377
|
return (
|
|
227
378
|
<div className="tableau-wrapper">
|
|
228
379
|
{loading && (
|
|
229
|
-
<div
|
|
230
|
-
className="tableau-loader"
|
|
231
|
-
style={{ ...(sheetSize.width ? { width: sheetSize.width } : {}) }}
|
|
232
|
-
>
|
|
380
|
+
<div className="tableau-loader">
|
|
233
381
|
<span>Loading...</span>
|
|
234
382
|
</div>
|
|
235
383
|
)}
|
|
384
|
+
{!loading && mode === 'edit' && (
|
|
385
|
+
<Button
|
|
386
|
+
className="reload-tableau"
|
|
387
|
+
icon
|
|
388
|
+
secondary
|
|
389
|
+
compact
|
|
390
|
+
onClick={() => {
|
|
391
|
+
if (tableau && url) {
|
|
392
|
+
disposeViz();
|
|
393
|
+
setInitiateViz(true);
|
|
394
|
+
}
|
|
395
|
+
}}
|
|
396
|
+
>
|
|
397
|
+
<Icon name={resetSVG} size="24px" />
|
|
398
|
+
</Button>
|
|
399
|
+
)}
|
|
236
400
|
<TableauDebug
|
|
237
401
|
mode={props.mode}
|
|
402
|
+
data={data}
|
|
238
403
|
vizState={{ loaded, loading, error }}
|
|
239
404
|
url={url}
|
|
240
405
|
version={version}
|
|
241
|
-
|
|
406
|
+
clearData={clearData}
|
|
242
407
|
/>
|
|
243
408
|
<div
|
|
244
409
|
className={cx('tableau', `tableau-${version}`, {
|
|
245
|
-
'tableau-
|
|
410
|
+
'tableau-autoscale': autoScale,
|
|
246
411
|
})}
|
|
247
412
|
ref={vizEl}
|
|
248
413
|
/>
|
|
249
|
-
<div
|
|
250
|
-
className="tableau-info"
|
|
251
|
-
style={{ ...(sheetSize.width ? { width: sheetSize.width } : {}) }}
|
|
252
|
-
>
|
|
414
|
+
<div className="tableau-info">
|
|
253
415
|
{with_sources && loaded && <Sources sources={sources} />}
|
|
254
416
|
{with_download && loaded && <Download viz={viz.current} />}
|
|
255
417
|
{with_share && loaded && <Share viz={viz.current} />}
|
|
256
418
|
</div>
|
|
257
419
|
</div>
|
|
258
420
|
);
|
|
259
|
-
};
|
|
421
|
+
});
|
|
260
422
|
|
|
261
|
-
export default connect(
|
|
262
|
-
|
|
263
|
-
|
|
423
|
+
export default connect(
|
|
424
|
+
(state) => ({
|
|
425
|
+
screen: state.screen,
|
|
426
|
+
}),
|
|
427
|
+
null,
|
|
428
|
+
null,
|
|
429
|
+
{ forwardRef: true },
|
|
430
|
+
)(Tableau);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import isUndefined from 'lodash/isUndefined';
|
|
2
|
+
|
|
3
|
+
export const getSheetnames = (viz) => {
|
|
4
|
+
if (!viz) return [];
|
|
5
|
+
let sheetnames = [];
|
|
6
|
+
const workbook = viz.getWorkbook();
|
|
7
|
+
workbook.getPublishedSheetsInfo().forEach((sheet) => {
|
|
8
|
+
const sheetName = sheet.getName();
|
|
9
|
+
sheetnames.push(sheetName);
|
|
10
|
+
});
|
|
11
|
+
return sheetnames;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getSheetnamesChoices = (viz) => {
|
|
15
|
+
return getSheetnames(viz).map((sheet) => [sheet, sheet]);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getActiveSheetname = (viz) => {
|
|
19
|
+
const workbook = viz.getWorkbook();
|
|
20
|
+
return workbook.getActiveSheet().getName();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const canChangeVizData = (viz, vizState) => {
|
|
24
|
+
// If viz is null it means that the viz is loading
|
|
25
|
+
// If viz is undefined it means that there is no viz nor it is loading
|
|
26
|
+
if (vizState?.loading) return false;
|
|
27
|
+
return !!viz || isUndefined(viz);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const getDevice = (breakpoints, width) => {
|
|
31
|
+
let device = 'desktop';
|
|
32
|
+
Object.keys(breakpoints).forEach((breakpoint) => {
|
|
33
|
+
if (
|
|
34
|
+
width <= breakpoints[breakpoint][0] &&
|
|
35
|
+
width >= breakpoints[breakpoint][1]
|
|
36
|
+
) {
|
|
37
|
+
device = breakpoint;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return device;
|
|
41
|
+
};
|
|
@@ -30,7 +30,7 @@ const Download = ({ viz }) => {
|
|
|
30
30
|
ref={popupRef}
|
|
31
31
|
>
|
|
32
32
|
<Button
|
|
33
|
-
|
|
33
|
+
className="primary"
|
|
34
34
|
onClick={() => {
|
|
35
35
|
viz.showExportImageDialog();
|
|
36
36
|
popupRef.current.triggerRef.current.click();
|
|
@@ -39,7 +39,7 @@ const Download = ({ viz }) => {
|
|
|
39
39
|
Image
|
|
40
40
|
</Button>
|
|
41
41
|
<Button
|
|
42
|
-
|
|
42
|
+
className="primary"
|
|
43
43
|
onClick={() => {
|
|
44
44
|
viz.showExportPDFDialog();
|
|
45
45
|
popupRef.current.triggerRef.current.click();
|
|
@@ -48,7 +48,7 @@ const Download = ({ viz }) => {
|
|
|
48
48
|
PDF
|
|
49
49
|
</Button>
|
|
50
50
|
<Button
|
|
51
|
-
|
|
51
|
+
className="primary"
|
|
52
52
|
onClick={() => {
|
|
53
53
|
viz.showExportCrossTabDialog();
|
|
54
54
|
popupRef.current.triggerRef.current.click();
|
|
@@ -57,7 +57,7 @@ const Download = ({ viz }) => {
|
|
|
57
57
|
CSV
|
|
58
58
|
</Button>
|
|
59
59
|
<Button
|
|
60
|
-
|
|
60
|
+
className="primary"
|
|
61
61
|
onClick={() => {
|
|
62
62
|
viz.exportCrossTabToExcel();
|
|
63
63
|
popupRef.current.triggerRef.current.click();
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import isObject from 'lodash/isObject';
|
|
3
|
+
import isArray from 'lodash/isArray';
|
|
4
|
+
import isString from 'lodash/isString';
|
|
5
|
+
import isBoolean from 'lodash/isBoolean';
|
|
6
|
+
import isNull from 'lodash/isNull';
|
|
7
|
+
import isUndefined from 'lodash/isUndefined';
|
|
8
|
+
|
|
9
|
+
const JsonCodeSnippet = ({ obj, depth = 1 }) => {
|
|
10
|
+
const keys = Object.keys(obj);
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<span className="left-curly-brace">{</span>
|
|
14
|
+
{!!keys.length && <br />}
|
|
15
|
+
{keys.map((key) => {
|
|
16
|
+
const value = obj[key];
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<React.Fragment key={`${key}-depth`}>
|
|
20
|
+
<span className="key" style={{ marginLeft: `${depth}rem` }}>
|
|
21
|
+
"{key}":
|
|
22
|
+
</span>
|
|
23
|
+
<span className="value">
|
|
24
|
+
|
|
25
|
+
{isObject(value) && !isArray(value) && (
|
|
26
|
+
<JsonCodeSnippet obj={value} depth={depth + 1} />
|
|
27
|
+
)}
|
|
28
|
+
{isArray(value) && JSON.stringify(value)}
|
|
29
|
+
{isString(value) && <>"{value}"</>}
|
|
30
|
+
{isBoolean(value) && <>{value}</>}
|
|
31
|
+
{isNull(value) && <>null</>}
|
|
32
|
+
{isUndefined(value) && <>undefined</>}
|
|
33
|
+
</span>
|
|
34
|
+
<br />
|
|
35
|
+
</React.Fragment>
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
38
|
+
<span
|
|
39
|
+
className="right-curly-brace"
|
|
40
|
+
style={{ marginLeft: !!keys.length ? `${depth - 1}rem` : 0 }}
|
|
41
|
+
>
|
|
42
|
+
}
|
|
43
|
+
</span>
|
|
44
|
+
</>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default JsonCodeSnippet;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Container } from 'semantic-ui-react';
|
|
3
|
+
import config from '@plone/volto/registry';
|
|
3
4
|
import Tableau from '@eeacms/volto-tableau/Tableau/Tableau';
|
|
4
5
|
|
|
5
6
|
const VisualizationView = (props) => {
|
|
@@ -9,11 +10,16 @@ const VisualizationView = (props) => {
|
|
|
9
10
|
return (
|
|
10
11
|
<Container id="page-document">
|
|
11
12
|
<Tableau
|
|
12
|
-
data={
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
data={{
|
|
14
|
+
...tableau_visualization,
|
|
15
|
+
with_sources: true,
|
|
16
|
+
with_download: true,
|
|
17
|
+
with_share: true,
|
|
18
|
+
}}
|
|
16
19
|
sources={data_provenance.data || []}
|
|
20
|
+
breakpoints={
|
|
21
|
+
config.blocks.blocksConfig.embed_tableau_visualization.breakpoints
|
|
22
|
+
}
|
|
17
23
|
/>
|
|
18
24
|
</Container>
|
|
19
25
|
);
|