diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx index 1f65e1c81..b6d0ff995 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx @@ -33,7 +33,7 @@ function CustomMetricOverviewChart(props: Props) { {gradientDef} diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx index e1af351ee..47e47e12e 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx @@ -47,7 +47,7 @@ function ResponseTime(props: Props) { /> */} - + @@ -115,6 +118,7 @@ function DashboardView(props: Props) { siteId={siteId} dashboardId={dashboardId} onEditHandler={onAddWidgets} + id="report" /> void; + id?: string; } function DashboardWidgetGrid(props) { const { dashboardId, siteId } = props; const { dashboardStore } = useStore(); const loading = useObserver(() => dashboardStore.isLoading); - const dashbaord: any = dashboardStore.selectedDashboard; - const list: any = useObserver(() => dashbaord?.widgets); + const dashboard: any = dashboardStore.selectedDashboard; + const list: any = useObserver(() => dashboard?.widgets); return useObserver(() => ( @@ -29,13 +30,13 @@ function DashboardWidgetGrid(props) { } > -
+
{list && list.map((item, index) => ( dashbaord.swapWidgetPosition(dragIndex, hoverIndex)} + moveListItem={(dragIndex, hoverIndex) => dashboard.swapWidgetPosition(dragIndex, hoverIndex)} dashboardId={dashboardId} siteId={siteId} isWidget={true} diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index b29ab800c..8f2d5b051 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -86,14 +86,15 @@ function WidgetWrapper(props: Props) { }} ref={dragDropRef} onClick={props.onClick ? props.onClick : () => {}} + id={`widget-${widget.widgetId}`} > {isTemplate && }
-

{widget.name}

+
{widget.name}
{isWidget && ( -
+
{!isPredefined && ( <> @@ -118,11 +119,11 @@ function WidgetWrapper(props: Props) { )}
- + {/* */}
-
+ {/*
*/}
)); } diff --git a/frontend/app/components/hocs/withReport.tsx b/frontend/app/components/hocs/withReport.tsx new file mode 100644 index 000000000..d5f77adc9 --- /dev/null +++ b/frontend/app/components/hocs/withReport.tsx @@ -0,0 +1,142 @@ +import React, { useEffect } from 'react'; +import { convertElementToImage } from 'App/utils'; +import { jsPDF } from "jspdf"; +import { useStore } from 'App/mstore'; +import { observer, useObserver } from 'mobx-react-lite'; +import { connect } from 'react-redux'; +interface Props { + site: any +} +export default function withReport

( + WrappedComponent: React.ComponentType

, +) { + const ComponentWithReport = (props: P) => { + const [rendering, setRendering] = React.useState(false); + const { site } = props; + const { dashboardStore } = useStore(); + const dashboard: any = useObserver(() => dashboardStore.selectedDashboard); + const widgets: any = useObserver(() => dashboard?.widgets); + const period = useObserver(() => dashboardStore.period); + console.log('site', site) + + const addFooters = (doc) => { + const pageCount = doc.internal.getNumberOfPages(); + for(var i = 1; i <= pageCount; i++) { + doc.setPage(i); + doc.setFontSize(10); + doc.setTextColor(136,136,136); + doc.text('Page ' + String(i) + ' of ' + String(pageCount), 196,285,null,null,"right"); + } + } + + const renderReport = async () => { + document.body.scrollIntoView(); + const doc = new jsPDF('p', 'mm', 'a4'); + + doc.addMetadata('Author', 'OpenReplay'); + doc.addMetadata('Title', 'OpenReplay Report'); + doc.addMetadata('Subject', 'OpenReplay Report'); + doc.addMetadata('Keywords', 'OpenReplay Report'); + doc.addMetadata('Creator', 'OpenReplay'); + doc.addMetadata('Producer', 'OpenReplay'); + doc.addMetadata('CreationDate', new Date().toISOString()); + + + const parentElement = document.getElementById('report') as HTMLElement; + const pageHeight = 1200; + const pagesCount = parentElement.offsetHeight / pageHeight; + const pages: Array = []; + for(let i = 0; i < pagesCount; i++) { + const page = document.createElement('div'); + page.classList.add('page'); + page.style.height = `${pageHeight}px`; + page.style.whiteSpace = 'no-wrap !important'; + + const childrens = Array.from(parentElement.children).filter((child) => { + const rect = child.getBoundingClientRect(); + const parentRect = parentElement.getBoundingClientRect(); + const top = rect.top - parentRect.top; + return top >= i * pageHeight && top < (i + 1) * pageHeight; + }); + if (childrens.length > 0) { + pages.push(childrens); + } + } + + const rportLayer = document.getElementById("report-layer"); + + pages.forEach(async (page, index) => { + const pageDiv = document.createElement('div'); + pageDiv.classList.add('grid', 'gap-4', 'grid-cols-4', 'items-start', 'pb-10', 'auto-rows-min', 'printable-report'); + pageDiv.id = `page-${index}`; + pageDiv.style.backgroundColor = '#f6f6f6'; + pageDiv.style.gridAutoRows = 'min-content'; + pageDiv.style.padding = '30px'; + pageDiv.style.height = '490mm'; + + if (index === 0) { + const header = document.getElementById('report-header')?.cloneNode(true) as HTMLElement; + header.classList.add('col-span-4'); + header.style.display = 'block'; + pageDiv.appendChild(header); + } + page.forEach((child) => { + pageDiv.appendChild(child.cloneNode(true)); + }) + rportLayer?.appendChild(pageDiv); + }) + + setTimeout(async () => { + for (let i = 0; i < pages.length; i++) { + const pageDiv = document.getElementById(`page-${i}`) as HTMLElement; + const pageImage = await convertElementToImage(pageDiv); + doc.addImage(pageImage, 'PNG', 0, 0, 210, 0); + if (i === pages.length - 1) { + addFooters(doc); + doc.save('report.pdf'); + rportLayer!.innerHTML = ''; + } else { + doc.addPage(); + } + } + }, 100) + } + + return ( + <> +

+
+
+ +
+
+ Project: {site && site.name} +
+
+
+
{dashboard && dashboard.name}
+
+ {period && (period.range.start.format('MMM Do YY') + ' - ' + period.range.end.format('MMM Do YY'))} +
+
+
+ +
+ + + ) + } + + return connect(state => ({ + site: state.getIn(['site', 'instance']), + }))(ComponentWithReport); +} \ No newline at end of file diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index 7e99468ad..245158049 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -260,8 +260,6 @@ p { } } - - .tippy-tooltip.openreplay-theme { background-color: $tealx; color: white; @@ -273,4 +271,29 @@ p { .tippy-tooltip.openreplay-theme .tippy-backdrop { background-color: $tealx; +} + +@media print { + .no-print { + display:none !important; + } +} + +.printable-report * { + white-space: nowrap !important; +} +.recharts-default-legend { + display: flex !important; + align-items: center; + justify-content: center; +} + +.recharts-legend-item { + display: flex !important; + align-items: center !important; + white-space: nowrap !important; +} + +.recharts-legend-item-text { + white-space: nowrap !important; } \ No newline at end of file diff --git a/frontend/app/svg/icons/filetype-pdf.svg b/frontend/app/svg/icons/filetype-pdf.svg new file mode 100644 index 000000000..e1fc9b698 --- /dev/null +++ b/frontend/app/svg/icons/filetype-pdf.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/report.pdf b/frontend/app/svg/icons/report.pdf new file mode 100644 index 000000000..33eab9271 Binary files /dev/null and b/frontend/app/svg/icons/report.pdf differ diff --git a/frontend/app/utils.js b/frontend/app/utils.js index 5f7e446fa..f7ae27b5b 100644 --- a/frontend/app/utils.js +++ b/frontend/app/utils.js @@ -1,5 +1,6 @@ import JSBI from 'jsbi'; import chroma from "chroma-js"; +import * as htmlToImage from 'html-to-image'; export function debounce(callback, wait, context = this) { let timeout = null; @@ -246,4 +247,16 @@ export const positionOfTheNumber = (min, max, value, length) => { const interval = (max - min) / length; const position = Math.round((value - min) / interval); return position; +} + +export const convertElementToImage = async (el) => { + const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el); + const image = await htmlToImage.toJpeg(el, { + pixelRatio: 2, + fontEmbedCss, + filter: function (node) { + return node.id !== 'no-print'; + }, + }); + return image; } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 75fcde026..294de4294 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,9 +15,11 @@ "codemirror": "^5.62.3", "copy-to-clipboard": "^3.3.1", "deep-diff": "^1.0.2", + "html-to-image": "^1.9.0", "immutable": "^4.0.0-rc.12", "jsbi": "^4.1.0", "jshint": "^2.11.1", + "jspdf": "^2.5.1", "luxon": "^1.24.1", "mobx": "^6.3.8", "mobx-react-lite": "^3.1.6", @@ -4679,7 +4681,7 @@ "version": "14.18.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.13.tgz", "integrity": "sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA==", - "dev": true + "devOptional": true }, "node_modules/@types/node-fetch": { "version": "2.6.1", @@ -4731,7 +4733,7 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "node_modules/@types/q": { "version": "1.5.5", @@ -4745,11 +4747,17 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "node_modules/@types/raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "optional": true + }, "node_modules/@types/react": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.6.tgz", "integrity": "sha512-bPqwzJRzKtfI0mVYr5R+1o9BOE8UEXefwc1LwcBtfnaAn6OoqMhLa/91VA8aeWfDPJt1kHvYKI8RHcQybZLHHA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4769,7 +4777,7 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "devOptional": true }, "node_modules/@types/resize-observer-browser": { "version": "0.1.7", @@ -4780,7 +4788,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "devOptional": true }, "node_modules/@types/source-list-map": { "version": "0.1.2", @@ -5625,7 +5633,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, "bin": { "atob": "bin/atob.js" }, @@ -6139,6 +6146,15 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6623,6 +6639,17 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -6908,6 +6935,25 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -7834,7 +7880,7 @@ "version": "3.22.2", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.2.tgz", "integrity": "sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "funding": { "type": "opencollective", @@ -8333,6 +8379,15 @@ "postcss": "^8.0.9" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", @@ -9385,6 +9440,12 @@ "domelementtype": "1" } }, + "node_modules/dompurify": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", + "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==", + "optional": true + }, "node_modules/domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", @@ -10858,6 +10919,11 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "dev": true }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -12655,6 +12721,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-to-image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.9.0.tgz", + "integrity": "sha512-9gaDCIYg62Ek07F2pBk76AHgYZ2gxq2YALU7rK3gNCqXuhu6cWzsOQqM7qGbjZiOzxGzrU1deDqZpAod2NEwbA==" + }, "node_modules/html-void-elements": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", @@ -12726,6 +12797,19 @@ "object-assign": "^4.0.1" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", @@ -14098,6 +14182,23 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", @@ -19986,6 +20087,15 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -20986,6 +21096,15 @@ "node": "*" } }, + "node_modules/stackblur-canvas": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", + "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", @@ -21487,6 +21606,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -22128,6 +22256,15 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "dev": true }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/throttle-debounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", @@ -23299,6 +23436,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -29063,7 +29209,7 @@ "version": "14.18.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.13.tgz", "integrity": "sha512-Z6/KzgyWOga3pJNS42A+zayjhPbf2zM3hegRQaOPnLOzEi86VV++6FLDWgR1LGrVCRufP/ph2daa3tEa5br1zA==", - "dev": true + "devOptional": true }, "@types/node-fetch": { "version": "2.6.1", @@ -29115,7 +29261,7 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "@types/q": { "version": "1.5.5", @@ -29129,11 +29275,17 @@ "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", "dev": true }, + "@types/raf": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz", + "integrity": "sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==", + "optional": true + }, "@types/react": { "version": "18.0.6", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.6.tgz", "integrity": "sha512-bPqwzJRzKtfI0mVYr5R+1o9BOE8UEXefwc1LwcBtfnaAn6OoqMhLa/91VA8aeWfDPJt1kHvYKI8RHcQybZLHHA==", - "dev": true, + "devOptional": true, "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -29144,7 +29296,7 @@ "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==", - "dev": true + "devOptional": true } } }, @@ -29166,7 +29318,7 @@ "version": "0.16.2", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", - "dev": true + "devOptional": true }, "@types/source-list-map": { "version": "0.1.2", @@ -29891,8 +30043,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autoprefixer": { "version": "7.2.6", @@ -30304,6 +30455,12 @@ "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -30683,6 +30840,11 @@ "picocolors": "^1.0.0" } }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -30911,6 +31073,22 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz", "integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==" }, + "canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "requires": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + } + }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -31656,7 +31834,7 @@ "version": "3.22.2", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.22.2.tgz", "integrity": "sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA==", - "dev": true + "devOptional": true }, "core-js-compat": { "version": "3.22.2", @@ -32073,6 +32251,15 @@ "dev": true, "requires": {} }, + "css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "optional": true, + "requires": { + "utrie": "^1.0.2" + } + }, "css-loader": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", @@ -32946,6 +33133,12 @@ "domelementtype": "1" } }, + "dompurify": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", + "integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==", + "optional": true + }, "domutils": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", @@ -34167,6 +34360,11 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "dev": true }, + "fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "figgy-pudding": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", @@ -35585,6 +35783,11 @@ "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz", "integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==" }, + "html-to-image": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.9.0.tgz", + "integrity": "sha512-9gaDCIYg62Ek07F2pBk76AHgYZ2gxq2YALU7rK3gNCqXuhu6cWzsOQqM7qGbjZiOzxGzrU1deDqZpAod2NEwbA==" + }, "html-void-elements": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.5.tgz", @@ -35638,6 +35841,16 @@ } } }, + "html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "optional": true, + "requires": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + } + }, "htmlparser2": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", @@ -36669,6 +36882,21 @@ "universalify": "^2.0.0" } }, + "jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "requires": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "fflate": "^0.4.8", + "html2canvas": "^1.0.0-rc.5" + } + }, "jsx-ast-utils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.2.tgz", @@ -41376,6 +41604,12 @@ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true }, + "rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=", + "optional": true + }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -42227,6 +42461,12 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, + "stackblur-canvas": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.5.0.tgz", + "integrity": "sha512-EeNzTVfj+1In7aSLPKDD03F/ly4RxEuF/EX0YcOG0cKoPXs+SLZxDawQbexQDBzwROs4VKLWTOaZQlZkGBFEIQ==", + "optional": true + }, "stackframe": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.1.tgz", @@ -42639,6 +42879,12 @@ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, + "svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true + }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -43125,6 +43371,15 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "dev": true }, + "text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "optional": true, + "requires": { + "utrie": "^1.0.2" + } + }, "throttle-debounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", @@ -44005,6 +44260,15 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", "dev": true }, + "utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "optional": true, + "requires": { + "base64-arraybuffer": "^1.0.2" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7f51ba174..6e683ad27 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,9 +22,11 @@ "codemirror": "^5.62.3", "copy-to-clipboard": "^3.3.1", "deep-diff": "^1.0.2", + "html-to-image": "^1.9.0", "immutable": "^4.0.0-rc.12", "jsbi": "^4.1.0", "jshint": "^2.11.1", + "jspdf": "^2.5.1", "luxon": "^1.24.1", "mobx": "^6.3.8", "mobx-react-lite": "^3.1.6",