@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
@@ -0,0 +1,224 @@
1
+ import {
2
+ getSheetnamesChoices,
3
+ canChangeVizData,
4
+ } from '@eeacms/volto-tableau/Tableau/helpers';
5
+
6
+ const urlParametersSchema = {
7
+ title: 'Parameter',
8
+ fieldsets: [
9
+ { id: 'default', title: 'Default', fields: ['field', 'urlParam'] },
10
+ ],
11
+ properties: {
12
+ field: {
13
+ title: 'Tableau fieldname',
14
+ type: 'text',
15
+ },
16
+ urlParam: {
17
+ title: 'URL param',
18
+ type: 'text',
19
+ },
20
+ },
21
+ required: [],
22
+ };
23
+
24
+ const staticParameters = {
25
+ title: 'Parameter',
26
+ fieldsets: [{ id: 'default', title: 'Default', fields: ['field', 'value'] }],
27
+ properties: {
28
+ field: {
29
+ title: 'Tableau fieldname',
30
+ type: 'text',
31
+ },
32
+ value: {
33
+ title: 'Value',
34
+ type: 'text',
35
+ },
36
+ },
37
+ required: [],
38
+ };
39
+
40
+ const breakpointUrlSchema = (config) => {
41
+ const breakpoints = config.blocks.blocksConfig.tableau_block.breakpoints;
42
+
43
+ return {
44
+ title: 'URL',
45
+ fieldsets: [{ id: 'default', title: 'Default', fields: ['device', 'url'] }],
46
+ properties: {
47
+ device: {
48
+ title: 'Device',
49
+ choices: Object.keys(breakpoints).map((breakpoint) => [
50
+ breakpoint,
51
+ breakpoint,
52
+ ]),
53
+ },
54
+ url: {
55
+ title: 'Url',
56
+ widget: 'textarea',
57
+ },
58
+ },
59
+ required: [],
60
+ };
61
+ };
62
+
63
+ const sourceSchema = {
64
+ title: 'Source',
65
+ fieldsets: [
66
+ {
67
+ id: 'default',
68
+ title: 'Default',
69
+ fields: ['title', 'organisation', 'link'],
70
+ },
71
+ ],
72
+ properties: {
73
+ title: {
74
+ title: 'Title',
75
+ },
76
+ organisation: {
77
+ title: 'Organization',
78
+ },
79
+ link: {
80
+ title: 'Link',
81
+ widget: 'url',
82
+ },
83
+ },
84
+ required: [],
85
+ };
86
+
87
+ export default (config, viz, vizState) => {
88
+ const isDisabled = !canChangeVizData(viz, vizState);
89
+
90
+ return {
91
+ title: 'Tableau',
92
+ fieldsets: [
93
+ {
94
+ id: 'default',
95
+ title: 'Default',
96
+ fields: [
97
+ 'url',
98
+ 'title',
99
+ 'description',
100
+ 'with_sources',
101
+ 'with_download',
102
+ 'with_share',
103
+ ],
104
+ },
105
+ {
106
+ id: 'options',
107
+ title: 'Options',
108
+ fields: [
109
+ 'sheetname',
110
+ 'hideTabs',
111
+ 'hideToolbar',
112
+ 'autoScale',
113
+ 'toolbarPosition',
114
+ ],
115
+ },
116
+ {
117
+ id: 'extra_options',
118
+ title: 'Extra options',
119
+ fields: ['urlParameters', 'staticParameters', 'breakpointUrls'],
120
+ },
121
+ {
122
+ id: 'sources',
123
+ title: 'Sources',
124
+ fields: ['sources'],
125
+ },
126
+ ],
127
+ properties: {
128
+ url: {
129
+ title: 'Url',
130
+ widget: 'textarea',
131
+ isDisabled,
132
+ },
133
+ title: {
134
+ title: 'Title',
135
+ widget: 'textarea',
136
+ },
137
+ description: {
138
+ title: 'Description',
139
+ widget: 'textarea',
140
+ },
141
+ with_download: {
142
+ title: 'Show download button',
143
+ type: 'boolean',
144
+ defaultValue: true,
145
+ },
146
+ with_share: {
147
+ title: 'Show share button',
148
+ type: 'boolean',
149
+ defaultValue: true,
150
+ },
151
+ with_sources: {
152
+ title: 'Show sources',
153
+ type: 'boolean',
154
+ defaultValue: true,
155
+ },
156
+ sources: {
157
+ title: 'Sources',
158
+ widget: 'object_list',
159
+ schema: sourceSchema,
160
+ },
161
+ sheetname: {
162
+ title: 'Sheetname',
163
+ choices: getSheetnamesChoices(viz),
164
+ isDisabled,
165
+ },
166
+ hideTabs: {
167
+ title: 'Hide tabs',
168
+ type: 'boolean',
169
+ default: false,
170
+ isDisabled,
171
+ },
172
+ hideToolbar: {
173
+ title: 'Hide toolbar',
174
+ type: 'boolean',
175
+ default: false,
176
+ isDisabled,
177
+ },
178
+ autoScale: {
179
+ title: 'Auto scale',
180
+ type: 'boolean',
181
+ default: false,
182
+ description: 'Scale down tableau according to width',
183
+ isDisabled,
184
+ },
185
+ toolbarPosition: {
186
+ title: 'Toolbar position',
187
+ choices: [
188
+ ['Top', 'Top'],
189
+ ['Bottom', 'Bottom'],
190
+ ],
191
+ default: 'Top',
192
+ isDisabled,
193
+ },
194
+ urlParameters: {
195
+ title: 'URL parameters',
196
+ widget: 'object_list',
197
+ schema: urlParametersSchema,
198
+ description: 'Set a list of url parameters to filter the tableau',
199
+ isDisabled,
200
+ },
201
+ staticParameters: {
202
+ title: 'Static parameters',
203
+ widget: 'object_list',
204
+ schema: staticParameters,
205
+ description: (
206
+ <>
207
+ Set a list of static parameters.
208
+ <br />
209
+ <b>NOTE: You need to trigger a refresh for this to take effect</b>
210
+ </>
211
+ ),
212
+ isDisabled,
213
+ },
214
+ breakpointUrls: {
215
+ title: 'Breakpoint urls',
216
+ widget: 'object_list',
217
+ schema: breakpointUrlSchema(config),
218
+ description: 'Set different vizualization for specific breakpoint',
219
+ isDisabled,
220
+ },
221
+ },
222
+ required: ['url'],
223
+ };
224
+ };
@@ -0,0 +1,9 @@
1
+ import installEmbedTableauVisualization from './EmbedTableauVisualization';
2
+ import installTableauBlock from './TableauBlock';
3
+
4
+ export default (config) => {
5
+ return [installEmbedTableauVisualization, installTableauBlock].reduce(
6
+ (acc, apply) => apply(acc),
7
+ config,
8
+ );
9
+ };
@@ -0,0 +1,430 @@
1
+ import React, {
2
+ useEffect,
3
+ useImperativeHandle,
4
+ useState,
5
+ useRef,
6
+ useMemo,
7
+ useCallback,
8
+ forwardRef,
9
+ } from 'react';
10
+ import { connect } from 'react-redux';
11
+ import { toast } from 'react-toastify';
12
+ import isEqual from 'lodash/isEqual';
13
+ import isUndefined from 'lodash/isUndefined';
14
+ import cx from 'classnames';
15
+ import { Button } from 'semantic-ui-react';
16
+ import { Toast, Icon } from '@plone/volto/components';
17
+ import { useTableau } from '@eeacms/volto-tableau/hooks';
18
+ import JsonCodeSnippet from '@eeacms/volto-tableau/Utils/JsonCodeSnippet/JsonCodeSnippet';
19
+ import Sources from '@eeacms/volto-tableau/Utils/Sources/Sources';
20
+ import Download from '@eeacms/volto-tableau/Utils/Download/Download';
21
+ import Share from '@eeacms/volto-tableau/Utils/Share/Share';
22
+ import { getSheetnames, getActiveSheetname, getDevice } from './helpers';
23
+
24
+ import resetSVG from '@plone/volto/icons/reset.svg';
25
+
26
+ const TableauDebug = ({ mode, data, vizState, url, version, clearData }) => {
27
+ const { loaded, error } = vizState;
28
+ const { filters = {}, parameters = {} } = data;
29
+
30
+ const showTableauInfo = mode === 'edit' && (!url || (loaded && url) || error);
31
+
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 && (
39
+ <h3 className="tableau-version">
40
+ Tableau <span className="version">{version}</span>
41
+ </h3>
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
+ )}
62
+ </div>
63
+ );
64
+ };
65
+
66
+ const Tableau = forwardRef((props, ref) => {
67
+ const vizEl = useRef(null);
68
+ const viz = useRef();
69
+ const vizState = useRef({});
70
+ const dataRef = useRef(props.data || {});
71
+ const [initiateViz, setInitiateViz] = useState(false);
72
+ const [loaded, setLoaded] = useState(false);
73
+ const [loading, setLoading] = useState(false);
74
+ const [error, setError] = useState(null);
75
+ const {
76
+ block,
77
+ data = {},
78
+ breakpoints = {},
79
+ extraFilters = {},
80
+ extraOptions = {},
81
+ mode = 'view',
82
+ screen = {},
83
+ version = '2.8.0',
84
+ sources,
85
+ setVizState,
86
+ onChangeBlock,
87
+ } = props;
88
+ const {
89
+ autoScale = false,
90
+ hideTabs = false,
91
+ hideToolbar = false,
92
+ sheetname = '',
93
+ toolbarPosition = 'Top',
94
+ breakpointUrls = [],
95
+ with_sources,
96
+ with_download,
97
+ with_share,
98
+ } = data;
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;
113
+
114
+ // Load tableau from script tag
115
+ const tableau = useTableau(version);
116
+
117
+ const onFilterChange = (filter) => {
118
+ let value;
119
+ const filters = dataRef.current.filters;
120
+ const newFilters = { ...filters };
121
+ const fieldName = filter.getFieldName();
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
+ }
148
+ if (!isEqual(newFilters, filters)) {
149
+ onChangeBlock(block, {
150
+ ...dataRef.current,
151
+ filters: {
152
+ ...newFilters,
153
+ },
154
+ });
155
+ }
156
+ };
157
+
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) => {
179
+ vizState.current = { ...vizState.current, loaded, loading, error };
180
+ setLoaded(loaded);
181
+ setLoading(loading);
182
+ setError(error);
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);
200
+ }
201
+ }, [onVizStateUpdate, sheetname]);
202
+
203
+ const clearData = () => {
204
+ onChangeBlock(block, {
205
+ ...data,
206
+ filters: {},
207
+ parameters: {},
208
+ });
209
+ };
210
+
211
+ const disposeViz = () => {
212
+ if (viz.current) {
213
+ viz.current.dispose();
214
+ viz.current = null;
215
+ }
216
+ onVizStateUpdate(false, false, null);
217
+ };
218
+
219
+ const initViz = () => {
220
+ try {
221
+ onVizStateUpdate(false, true, vizState.current.error);
222
+ viz.current = new tableau.Viz(vizEl.current, url, {
223
+ hideTabs,
224
+ hideToolbar,
225
+ toolbarPosition,
226
+ device: !!breakpointUrl ? device : 'desktop',
227
+ ...data.filters,
228
+ ...data.parameters,
229
+ ...extraFilters,
230
+ ...extraOptions,
231
+ onFirstInteractive: () => {
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();
239
+ const newData = {
240
+ ...data,
241
+ };
242
+ newData.url = vizUrl;
243
+ if (!sheetname || !sheetnames.includes(sheetname)) {
244
+ newData.sheetname = activeSheetname;
245
+ }
246
+ if (newData.url !== url || newData.sheetname !== sheetname) {
247
+ onChangeBlock(block, {
248
+ ...data,
249
+ ...newData,
250
+ });
251
+ toast.success(
252
+ <Toast success title={'Tableau data updated'} content={null} />,
253
+ );
254
+ }
255
+ }
256
+ if (viz.current && mode === 'edit') {
257
+ // Filter change event
258
+ viz.current.addEventListener(
259
+ tableau.TableauEventName.FILTER_CHANGE,
260
+ (event) => {
261
+ event.getFilterAsync().then((filter) => {
262
+ onFilterChange(filter);
263
+ });
264
+ },
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
+ );
275
+ }
276
+ },
277
+ });
278
+ } catch (e) {
279
+ onVizStateUpdate(false, false, e.get_message());
280
+ setInitiateViz(false);
281
+ }
282
+ };
283
+
284
+ const addExtraFilters = (extraFilters) => {
285
+ const worksheets =
286
+ viz.current.getWorkbook().getActiveSheet().getWorksheets() || [];
287
+
288
+ worksheets.forEach((worksheet) => {
289
+ if (worksheet.getSheetType() === tableau.DashboardObjectType.WORKSHEET) {
290
+ Object.keys(extraFilters).forEach((filter) => {
291
+ if (!extraFilters[filter]) {
292
+ worksheet.clearFilterAsync(filter);
293
+ } else {
294
+ worksheet.applyFilterAsync(
295
+ filter,
296
+ extraFilters[filter],
297
+ tableau.FilterUpdateType.REPLACE,
298
+ );
299
+ }
300
+ });
301
+ }
302
+ });
303
+ };
304
+
305
+ const updateScale = () => {
306
+ const tableauEl = vizEl.current.querySelector('iframe');
307
+ const { sheetSize = {} } = viz.current.getVizSize() || {};
308
+ const vizWidth = sheetSize?.minSize?.width || 1;
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
+ });
316
+ };
317
+
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);
328
+ } else {
329
+ disposeViz();
330
+ }
331
+ /* eslint-disable-next-line */
332
+ }, [tableau, url, hideTabs, hideToolbar, toolbarPosition]);
333
+
334
+ useEffect(() => {
335
+ if (initiateViz && !loaded && !loading) {
336
+ initViz();
337
+ }
338
+ /* eslint-disable-next-line */
339
+ }, [loaded, loading, initiateViz]);
340
+
341
+ useEffect(() => {
342
+ if (vizState.current.loaded && viz.current) {
343
+ addExtraFilters(extraFilters);
344
+ }
345
+ /* eslint-disable-next-line */
346
+ }, [JSON.stringify(extraFilters)]);
347
+
348
+ useEffect(() => {
349
+ if (vizState.current.loaded && viz.current && autoScale) {
350
+ updateScale();
351
+ }
352
+ /* eslint-disable-next-line */
353
+ }, [loaded, screen?.page?.width]);
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
+
377
+ return (
378
+ <div className="tableau-wrapper">
379
+ {loading && (
380
+ <div className="tableau-loader">
381
+ <span>Loading...</span>
382
+ </div>
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
+ )}
400
+ <TableauDebug
401
+ mode={props.mode}
402
+ data={data}
403
+ vizState={{ loaded, loading, error }}
404
+ url={url}
405
+ version={version}
406
+ clearData={clearData}
407
+ />
408
+ <div
409
+ className={cx('tableau', `tableau-${version}`, {
410
+ 'tableau-autoscale': autoScale,
411
+ })}
412
+ ref={vizEl}
413
+ />
414
+ <div className="tableau-info">
415
+ {with_sources && loaded && <Sources sources={sources} />}
416
+ {with_download && loaded && <Download viz={viz.current} />}
417
+ {with_share && loaded && <Share viz={viz.current} />}
418
+ </div>
419
+ </div>
420
+ );
421
+ });
422
+
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
+ };