diff --git a/front-end-components/src/components/charts/horizontal-bar-chart/component.tsx b/front-end-components/src/components/charts/horizontal-bar-chart/component.tsx index b5a404d84..7cdbcb090 100644 --- a/front-end-components/src/components/charts/horizontal-bar-chart/component.tsx +++ b/front-end-components/src/components/charts/horizontal-bar-chart/component.tsx @@ -250,7 +250,7 @@ function HorizontalBarChartInner(
diff --git a/front-end-components/src/components/charts/line-chart/component.tsx b/front-end-components/src/components/charts/line-chart/component.tsx index e653a5b17..2b8032f6b 100644 --- a/front-end-components/src/components/charts/line-chart/component.tsx +++ b/front-end-components/src/components/charts/line-chart/component.tsx @@ -195,7 +195,7 @@ function LineChartInner( // a11y: https://github.com/recharts/recharts/issues/3816
diff --git a/front-end-components/src/components/charts/vertical-bar-chart/component.tsx b/front-end-components/src/components/charts/vertical-bar-chart/component.tsx index 9ae175e02..7f0d21c56 100644 --- a/front-end-components/src/components/charts/vertical-bar-chart/component.tsx +++ b/front-end-components/src/components/charts/vertical-bar-chart/component.tsx @@ -131,7 +131,7 @@ function VerticalBarChartInner( // a11y: https://github.com/recharts/recharts/issues/3816
diff --git a/front-end-components/src/components/progress/component.tsx b/front-end-components/src/components/progress/component.tsx new file mode 100644 index 000000000..d15cb29e5 --- /dev/null +++ b/front-end-components/src/components/progress/component.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import "src/components/progress/styles.css"; +import { ProgressProps } from "."; +import classNames from "classnames"; + +// inspired by https://medium.com/@pppped/how-to-code-a-responsive-circular-percentage-chart-with-svg-and-css-3632f8cd7705 +export const Progress: React.FC = ({ + className, + percentage, + ...props +}) => { + return ( + + + + ); +}; diff --git a/front-end-components/src/components/progress/index.tsx b/front-end-components/src/components/progress/index.tsx new file mode 100644 index 000000000..aaba48ab3 --- /dev/null +++ b/front-end-components/src/components/progress/index.tsx @@ -0,0 +1,3 @@ +/* eslint-disable react-refresh/only-export-components */ +export * from "src/components/progress/component"; +export * from "src/components/progress/types"; diff --git a/front-end-components/src/components/progress/styles.css b/front-end-components/src/components/progress/styles.css new file mode 100644 index 000000000..0ec7913a5 --- /dev/null +++ b/front-end-components/src/components/progress/styles.css @@ -0,0 +1,24 @@ +.progress-wrapper { + display: block; + max-width: 80%; + max-height: 250px; +} + +.progress-wrapper.progress-left { + float: left; + margin-right: 10px; +} + +.progress-wrapper .progress-circle { + stroke: #005EA5; + fill: none; + stroke-width: 6; + stroke-linecap: round; + animation: progress 1s ease-out forwards; +} + +@keyframes progress { + 0% { + stroke-dasharray: 0 100; + } +} \ No newline at end of file diff --git a/front-end-components/src/components/progress/types.tsx b/front-end-components/src/components/progress/types.tsx new file mode 100644 index 000000000..611fc76bf --- /dev/null +++ b/front-end-components/src/components/progress/types.tsx @@ -0,0 +1,8 @@ +import { SVGProps } from "react"; + +export type ProgressProps = Pick< + SVGProps, + "width" | "height" | "className" +> & { + percentage: number; +}; diff --git a/front-end-components/src/components/share-content-by-elements/component.tsx b/front-end-components/src/components/share-content-by-elements/component.tsx index bd11ff2f0..c7aaf18a7 100644 --- a/front-end-components/src/components/share-content-by-elements/component.tsx +++ b/front-end-components/src/components/share-content-by-elements/component.tsx @@ -1,30 +1,50 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { ShareContentByElementsProps } from "./types"; import { useDownloadPngImages } from "src/hooks/useDownloadImage"; import { ShareContent } from "../share-content"; +import { Progress } from "../progress"; export const ShareContentByElements: React.FC = ({ disabled, elementsSelector, + showProgress, showTitles, label, ...props }) => { const [imagesLoading, setImagesLoading] = useState(); + const [progress, setProgress] = useState(); const downloadPngs = useDownloadPngImages({ elementsSelector, onImagesLoading: setImagesLoading, + onProgress: setProgress, showTitles, }); + useEffect(() => { + if (!imagesLoading) { + setProgress(undefined); + } + }, [imagesLoading]); + return ( await downloadPngs()} {...props} > - {label} + <> + {showProgress && progress && ( + + )} + {label} + ); }; diff --git a/front-end-components/src/components/share-content-by-elements/types.tsx b/front-end-components/src/components/share-content-by-elements/types.tsx index 490df28ae..f94b7d169 100644 --- a/front-end-components/src/components/share-content-by-elements/types.tsx +++ b/front-end-components/src/components/share-content-by-elements/types.tsx @@ -11,5 +11,6 @@ export type ShareContentByElementsProps = Omit< > & { elementsSelector: () => ElementAndTitle[]; label: string; + showProgress?: boolean; showTitles?: boolean; }; diff --git a/front-end-components/src/hooks/useDownloadImage.ts b/front-end-components/src/hooks/useDownloadImage.ts index 07722fc4a..3798adbc5 100644 --- a/front-end-components/src/hooks/useDownloadImage.ts +++ b/front-end-components/src/hooks/useDownloadImage.ts @@ -20,6 +20,7 @@ type ElementAndTitle = { export type DownloadPngImagesOptions = { elementsSelector: () => ElementAndTitle[]; onImagesLoading?: (loading: boolean) => void; + onProgress?: (percentage: number) => void; showTitles?: boolean; } & Pick; @@ -92,6 +93,7 @@ export function useDownloadPngImages({ elementsSelector, filter, onImagesLoading, + onProgress, showTitles, }: DownloadPngImagesOptions) { const fileName = "download.zip"; @@ -107,6 +109,9 @@ export function useDownloadPngImages({ for (let i = 0; i < elements.length; i++) { const { element, title } = elements[i]; + if (onProgress) { + onProgress(((i + 1) / elements.length) * 100); + } const blob = await ImageService.toBlob( element, @@ -149,7 +154,7 @@ export function useDownloadPngImages({ } else { await download(); } - }, [elementsSelector, filter, onImagesLoading, showTitles]); + }, [elementsSelector, filter, onImagesLoading, onProgress, showTitles]); return downloadPng; } diff --git a/front-end-components/src/main.tsx b/front-end-components/src/main.tsx index 7cf1e8332..c3d5d13e7 100644 --- a/front-end-components/src/main.tsx +++ b/front-end-components/src/main.tsx @@ -901,36 +901,39 @@ const shareContentByElementClassNameElements = if (shareContentByElementClassNameElements) { shareContentByElementClassNameElements.forEach((element) => { - const { elementClassName, elementTitleAttr, label, showTitles } = - element.dataset; + const { + elementClassName, + elementTitleAttr, + label, + showProgress, + showTitles, + } = element.dataset; if (elementClassName && label) { - const el = document.getElementsByClassName(elementClassName); - if (el.length > 0) { - const root = ReactDOM.createRoot(element); - root.render( - - { - const results = []; - const elements = - document.getElementsByClassName(elementClassName); - - for (let i = 0; i < elements.length; i++) { - const element = elements[i] as HTMLElement; - const title = elementTitleAttr - ? element.getAttribute(elementTitleAttr) || undefined - : undefined; - results.push({ element, title }); - } + const root = ReactDOM.createRoot(element); + root.render( + + { + const results = []; + const elements = + document.getElementsByClassName(elementClassName); + + for (let i = 0; i < elements.length; i++) { + const element = elements[i] as HTMLElement; + const title = elementTitleAttr + ? element.getAttribute(elementTitleAttr) || undefined + : undefined; + results.push({ element, title }); + } - return results; - }} - label={label} - showTitles={showTitles === "true"} - /> - - ); - } + return results; + }} + label={label} + showProgress={showProgress === "true"} + showTitles={showTitles === "true"} + /> + + ); } }); }