diff --git a/src/Blocks/EmbedEEATableauBlock/View.jsx b/src/Blocks/EmbedEEATableauBlock/View.jsx index 1c2b692..2a4b829 100644 --- a/src/Blocks/EmbedEEATableauBlock/View.jsx +++ b/src/Blocks/EmbedEEATableauBlock/View.jsx @@ -16,7 +16,6 @@ const View = (props) => { const data = React.useMemo(() => props.data || {}, [props.data]); const { vis_url = '' } = data; const show_sources = data?.show_sources ?? false; - const version = '2.8.0'; React.useEffect(() => { if (vis_url) { @@ -39,22 +38,12 @@ const View = (props) => { <> {tableau_visualization?.general?.url ? ( <> -
- {props.mode === 'edit' ? ( -
-

- == Tableau {version} loaded == -

-
- ) : ( - '' - )} - -
+ + {show_sources && data_provenance && data_provenance?.data?.data_provenance && diff --git a/src/ConnectedTableau/ConnectedTableau.jsx b/src/ConnectedTableau/ConnectedTableau.jsx index 094cd20..7addc63 100644 --- a/src/ConnectedTableau/ConnectedTableau.jsx +++ b/src/ConnectedTableau/ConnectedTableau.jsx @@ -6,6 +6,13 @@ const ConnectedTableau = (props) => { const [loaded, setLoaded] = React.useState(null); return (
+ {loaded && props.mode === 'edit' ? ( +
+

== Tableau ==

+
+ ) : ( + '' + )} { + const [open, setOpen] = React.useState(false); + const viz = props.viz || {}; + + const exportImage = () => { + viz.showExportImageDialog(); + }; + + const exportToCSV = () => { + viz.showExportCrossTabDialog(); + }; + + const exportToExcel = () => { + viz.exportCrossTabToExcel(); + }; + + return ( + <> + + + Save +
+ } + > + Download + +

Select your file format.

+ + + +
+ + + setOpen(false)} open={open}> + + Permissions are required to download the workbook. + + + + + + + + ); +}; + +export default TableauDownload; diff --git a/src/DownloadExtras/TableauFullscreen.jsx b/src/DownloadExtras/TableauFullscreen.jsx new file mode 100644 index 0000000..6b93aaa --- /dev/null +++ b/src/DownloadExtras/TableauFullscreen.jsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { Modal, Button } from 'semantic-ui-react'; +import { Icon } from '@plone/volto/components'; +import { useHistory, useLocation } from 'react-router-dom'; +import fullscreenSVG from '@plone/volto/icons/fullscreen.svg'; + +import config from '@plone/volto/registry'; + +const TableauFullscreen = (props) => { + const tableau_url = props.data.url; + const modalHash = props?.item.getId + '_preview'; + const [open, setOpen] = React.useState(false); + const history = useHistory(); + const location = useLocation(); + const { + blocks: { blocksConfig }, + } = config; + const TableauBlockView = blocksConfig.tableau_block.view; + + React.useEffect(() => { + if (location.hash.includes(modalHash)) { + setOpen(true); + } else { + setOpen(false); + } + }, [location, modalHash]); + + const closeModal = () => { + history.push({ + hash: '', + }); + setOpen(false); + }; + + return ( + <> +
+ + Enlarge +
+ + setOpen(true)} + open={open} + > + + + + + + + + + + ); +}; + +export default TableauFullscreen; diff --git a/src/DownloadExtras/TableauShare.jsx b/src/DownloadExtras/TableauShare.jsx new file mode 100644 index 0000000..290016d --- /dev/null +++ b/src/DownloadExtras/TableauShare.jsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Popup, Tab, Button, Menu, Input } from 'semantic-ui-react'; +import { Icon } from '@plone/volto/components'; +import useCopyToClipboard from '../downloadHelpers/downloadHelpers'; + +import shareSVG from '@plone/volto/icons/share.svg'; +import linkSVG from '@plone/volto/icons/link.svg'; + +import cx from 'classnames'; + +const TableauShare = (props) => { + const tableau_url = props.data.url; + + const CopyUrlButton = ({ url, buttonText }) => { + const [copyUrlStatus, copyUrl] = useCopyToClipboard(url); + + if (copyUrlStatus === 'copied') { + buttonText = 'Copied!'; + } else if (copyUrlStatus === 'failed') { + buttonText = 'Copy failed. Please try again.'; + } + + return ( + + ); + }; + + const panes = [ + { + menuItem: ( + + + + + URL + + ), + render: () => ( + + + + + ), + }, + ]; + + return ( + + + Share + + } + > + Share Visualization + + + + + ); +}; + +export default TableauShare; diff --git a/src/DownloadExtras/style.less b/src/DownloadExtras/style.less new file mode 100644 index 0000000..8a5abbc --- /dev/null +++ b/src/DownloadExtras/style.less @@ -0,0 +1,152 @@ +.dashboard-wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + background-color: #f5f5f5; + + .tableau-block { + position: relative; + display: flex; + flex: 1 1; + flex-direction: column; + padding: 1em 0.5em; + } + + .toolbar-button { + position: relative; + width: 32px; + height: 32px; + padding: 2px !important; + background-color: #ff421b !important; + border-radius: 3px !important; + color: white !important; + + &:hover { + opacity: 0.6; + } + + svg { + position: absolute; + top: 50%; + left: 50%; + margin: 0 !important; + fill: #fff !important; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + } + } +} + +.tableau-download-dialog { + max-width: 282px !important; + + .header { + padding: 0.5em 0 !important; + } + + button { + display: block; + width: 100%; + margin: 5px 0 !important; + } +} + +.tableau-share-dialog { + min-width: 400px !important; + + .ui.secondary.pointing.menu .item { + display: flex; + flex-direction: column; + padding: 10px 25px; + } + + .nav-dot-title { + margin-top: 5px; + font-size: 14px; + } + + .ui.secondary.pointing.menu .active.item { + border-color: #09b; + + .nav-dot-title { + font-weight: 500; + } + + .nav-dot { + opacity: 1; + } + } + + .nav-dot { + display: flex; + width: 37px; + height: 37px; + align-items: center; + justify-content: center; + margin: 8px auto; + background-color: #09b; + border-radius: 50%; + opacity: 0.6; + + svg { + fill: #fff !important; + } + } + + .ui.secondary.pointing.menu .active.item:hover { + border-color: #09b; + } + + .ui.tab { + .ui.input { + display: block; + margin-bottom: 1em; + + input { + width: 100%; + } + } + + textarea { + display: block; + width: 100%; + height: 100px; + padding: 6px 12px; + border: 1px solid grey; + margin-bottom: 1.5em; + color: #696969; + font-family: 'Lucida Console', Monaco, monospace; + font-size: 13px; + } + } +} + +.copy-button { + margin-top: 1rem !important; +} + +.copy-button.green-button { + background-color: #269b65 !important; +} + +.toolbar-button-wrapper { + display: flex; + flex-direction: column; + text-align: center; + + .btn-text { + font-family: Roboto, Helvetica Neue, Arial, Helvetica, sans-serif; + font-size: 12px; + font-weight: 400; + line-height: 20px; + } + + .ui.button.toolbar-button { + margin: 0 7px !important; + } +} + +.tableau-icons { + display: flex; + flex-direction: row; +} diff --git a/src/Tableau/View.jsx b/src/Tableau/View.jsx index 47568ac..da43db2 100644 --- a/src/Tableau/View.jsx +++ b/src/Tableau/View.jsx @@ -6,6 +6,9 @@ import { Toast } from '@plone/volto/components'; import { setTableauApi } from '@eeacms/volto-tableau/actions'; import cx from 'classnames'; import { loadTableauScript } from '../helpers'; +import TableauDownload from '../DownloadExtras/TableauDownload'; +import TableauShare from '../DownloadExtras/TableauShare'; +import '../DownloadExtras/style.less'; const Tableau = (props) => { const ref = React.useRef(null); @@ -212,23 +215,29 @@ const Tableau = (props) => { '' ) : (
- - {mode === 'edit' - ? 'Loading...' - : `Loading Tableau v${version}`} - + Loading...
)} ) : (
No data present in that visualization.
)} -
+
+
+ {viz ? ( +
+ + +
+ ) : null} +
+
+
); diff --git a/src/TableauBlock/View.jsx b/src/TableauBlock/View.jsx index f39dc98..c61e7b5 100644 --- a/src/TableauBlock/View.jsx +++ b/src/TableauBlock/View.jsx @@ -39,7 +39,6 @@ const View = (props) => { (breakpoint) => breakpoint.device === device, )[0]?.url; const url = breakpointUrl || data.url; - const version = '2.8.0'; React.useEffect(() => { setMounted(true); @@ -67,8 +66,8 @@ const View = (props) => { return mounted ? (
- {url && props.mode === 'edit' ? ( -

== Tableau {version} loaded ==

+ {loaded && url && props.mode === 'edit' ? ( +

== Tableau ==

) : null} {!url ?

URL required

: ''} {error ?

{error}

: ''} @@ -90,7 +89,6 @@ const View = (props) => { loaded={loaded} setError={setError} setLoaded={setLoaded} - version={version} url={url} /> ) : null} diff --git a/src/Widgets/VisualizationWidget.jsx b/src/Widgets/VisualizationWidget.jsx index 12256e0..14b53ad 100644 --- a/src/Widgets/VisualizationWidget.jsx +++ b/src/Widgets/VisualizationWidget.jsx @@ -53,14 +53,6 @@ const VisualizationWidget = (props) => { let schema = Schema(config); React.useEffect(() => { - // if (!intValue?.general) { - // setIntValue({ - // ...intValue, - // general: { - // url: intValue?.general?.url, - // }, - // }); - // } if (!intValue?.options) { setIntValue({ ...intValue, diff --git a/src/downloadHelpers/downloadHelpers.js b/src/downloadHelpers/downloadHelpers.js new file mode 100644 index 0000000..4d3e069 --- /dev/null +++ b/src/downloadHelpers/downloadHelpers.js @@ -0,0 +1,25 @@ +import React from 'react'; + +const useCopyToClipboard = (text) => { + const [copyStatus, setCopyStatus] = React.useState('inactive'); + const copy = React.useCallback(() => { + navigator.clipboard.writeText(text).then( + () => setCopyStatus('copied'), + () => setCopyStatus('failed'), + ); + }, [text]); + + React.useEffect(() => { + if (copyStatus === 'inactive') { + return; + } + + const timeout = setTimeout(() => setCopyStatus('inactive'), 3000); + + return () => clearTimeout(timeout); + }, [copyStatus]); + + return [copyStatus, copy]; +}; + +export default useCopyToClipboard;