From 8547a663cdabe409430e4fe6f7fe78931bb8e886 Mon Sep 17 00:00:00 2001 From: vikas-cldcvr Date: Mon, 11 Mar 2024 12:13:44 +0530 Subject: [PATCH 1/5] FDS-653 Dashboard as pdf report export feature --- package.json | 2 +- .../src/components/f-dashboard/f-dashboard.ts | 2 +- .../f-timeseries-chart/f-timeseries-chart.ts | 4 +- .../f-table-schema/f-table-schema.ts | 7 +- pnpm-lock.yaml | 8 +- stories/flow-dashboard/f-dashboard.stories.ts | 122 +++++++++++------- 6 files changed, 88 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index ed573845c..5e300f43f 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "@faker-js/faker": "^8.3.1", "@ollion/custom-elements-manifest-to-types": "workspace:*", "@ollion/prettier-config": "^2.1.0", - "@storybook/addon-a11y": "^7.6.12", "@storybook/addon-actions": "^7.5.3", "@storybook/addon-essentials": "^7.5.3", "@storybook/addon-links": "^7.5.3", @@ -84,6 +83,7 @@ "@ollion/flow-system-icon": "latest", "@ollion/flow-table": "workspace:*", "d3": "^7.6.1", + "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "lit": "^3.1.0" }, diff --git a/packages/flow-dashboard/src/components/f-dashboard/f-dashboard.ts b/packages/flow-dashboard/src/components/f-dashboard/f-dashboard.ts index dbe44bcfe..59fa5bb7f 100644 --- a/packages/flow-dashboard/src/components/f-dashboard/f-dashboard.ts +++ b/packages/flow-dashboard/src/components/f-dashboard/f-dashboard.ts @@ -50,7 +50,7 @@ export class FDashboard extends FRoot { render() { return html`
- ${this.config.widgets.map(wgt => { + ${this.config?.widgets.map(wgt => { return keyed( wgt.id, html`
= createRef(); chartLegends: Ref = createRef(); @@ -252,7 +252,7 @@ export class FTimeseriesChart extends FRoot { height="100%" > ${svg``} + >${svg``} ) => this.handleHeaderInput(event, columnHeader[1])} @@ -297,7 +296,7 @@ export class FTableSchema extends FRoot { .actions=${actions} .align=${cell.align} data-background="${this.stickyCellBackground}" - ?sticky-left=${ifDefined(sticky)} + ?sticky-left=${sticky} >${this.getCellTemplate(row.data[columnHeader[0]], highlightTerm)} `; })} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73c2d5c77..bd1c581e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: d3: specifier: ^7.6.1 version: 7.8.5 + html2canvas: + specifier: ^1.4.1 + version: 1.4.1 jspdf: specifier: ^2.5.1 version: 2.5.1 @@ -6883,7 +6886,6 @@ packages: engines: {node: '>= 0.6.0'} requiresBuild: true dev: false - optional: true /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -7713,7 +7715,6 @@ packages: dependencies: utrie: 1.0.2 dev: false - optional: true /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -9705,7 +9706,6 @@ packages: css-line-break: 2.1.0 text-segmentation: 1.0.3 dev: false - optional: true /http-assert@1.5.0: resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} @@ -13830,7 +13830,6 @@ packages: dependencies: utrie: 1.0.2 dev: false - optional: true /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -14418,7 +14417,6 @@ packages: dependencies: base64-arraybuffer: 1.0.2 dev: false - optional: true /uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} diff --git a/stories/flow-dashboard/f-dashboard.stories.ts b/stories/flow-dashboard/f-dashboard.stories.ts index 657ae5dbf..c77ffec63 100644 --- a/stories/flow-dashboard/f-dashboard.stories.ts +++ b/stories/flow-dashboard/f-dashboard.stories.ts @@ -1,14 +1,11 @@ import { Meta } from "@storybook/web-components"; import { html } from "lit-html"; -import { - FDashboard, - FDashboardConfig, - FDashboardWidget, - FTimeseriesChartConfig -} from "@ollion/flow-dashboard"; +import type { FDashboard, FDashboardConfig, FDashboardWidget } from "@ollion/flow-dashboard"; import { generateTimeseriesChartData } from "./mock-data-utils"; import { faker } from "@faker-js/faker"; import { createRef, ref } from "lit/directives/ref.js"; +import html2canvas from "html2canvas"; +import jsPDF from "jspdf"; export default { title: "@ollion/flow-dashboard/f-dashboard", @@ -40,41 +37,11 @@ const getWidgets = () => { data: generateTimeseriesChartData(startFrom) }, id: faker.string.alpha(10), - header() { - const name = faker.company.name(); - const description = faker.lorem.sentences(3); - return html` - - - ${name} - ${description} - - `; - }, - footer: () => { - const date = faker.date.recent({ refDate: new Date() }); - const state = faker.helpers.arrayElement(["danger", "success", "warning"]); - return html` - - - Last updated on ${date.toLocaleDateString()} ${date.toLocaleTimeString()} - - - `; + header: { + title: faker.company.name(), + description: faker.lorem.sentences(3) }, + footer: `Powered by Flow`, placement: { w: faker.number.int({ min: 4, max: 8 }), h: faker.number.int({ min: 3, max: 4 }) @@ -103,6 +70,7 @@ const getWidgets = () => { }; const Template = () => { const dashboardRef = createRef(); + const imgRef = createRef(); const dashboardConfig: FDashboardConfig = { widgets: getWidgets() }; @@ -114,7 +82,67 @@ const Template = () => { } }; - return html` + /** + * Download file as image in pdf + * + * + */ + // const downloadFile = () => { + // const element = document.querySelector("#dashboard-to-export") as FDashboard; + + // html2canvas(element, { scale: 1 }).then(function (canvas) { + // // Initialize jsPDF + // const pdf = new jsPDF({ + // orientation: "p", + // unit: "px", + // format: [element.scrollWidth, element.scrollHeight] + // }); + // pdf.setDisplayMode("original"); + // //const dataURL = canvas.toDataURL("image/png", 1.0); + // // Add canvas image to PDF + // //imgRef.value!.src = dataURL; + + // pdf.addImage(canvas, "PNG", 0, 0, element.scrollWidth, element.scrollHeight); + + // // Save the PDF + // pdf.save("canvas_to_pdf.pdf"); + // }); + // }; + + const downloadFile = () => { + // const allSVGS = document.querySelectorAll("svg"); + // console.log(allSVGS); + // for (let i = 0; i < allSVGS.length; i++) { + // console.log(allSVGS[i]); + // html2canvas(allSVGS.item(i) as unknown as HTMLElement, { scale: 1 }).then(function (canvas) { + // allSVGS[i].outerHTML = ``; + // }); + // } + + const element = document.querySelector("#dashboard-to-export") as FDashboard; + const doc = new jsPDF({ + orientation: "p", + unit: "px", + format: [element.scrollWidth, element.scrollHeight] + }); + + doc.html(element, { + html2canvas: { + scale: 1, + async: true, + svgRendering: true + }, + callback: function (doc) { + doc.save("sample-document.pdf"); + }, + width: element.scrollWidth, + windowWidth: 2400 + }); + }; + + return html` + + { gap="auto" align="middle-left" > - Click on randomize button to generate new data - + Click on export button to generate pdf + - + + `; }; From 31069e4bac300906a961531153db05953e00d265 Mon Sep 17 00:00:00 2001 From: vikas-cldcvr Date: Mon, 11 Mar 2024 14:43:52 +0530 Subject: [PATCH 2/5] FDS-653 rebased --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5e300f43f..f151e4e6b 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@faker-js/faker": "^8.3.1", "@ollion/custom-elements-manifest-to-types": "workspace:*", "@ollion/prettier-config": "^2.1.0", + "@storybook/addon-a11y": "^7.6.12", "@storybook/addon-actions": "^7.5.3", "@storybook/addon-essentials": "^7.5.3", "@storybook/addon-links": "^7.5.3", From 91d6d5ceaf02e67992bfc3c6f2e3d51bf5120c8e Mon Sep 17 00:00:00 2001 From: vikas-cldcvr Date: Tue, 12 Mar 2024 10:46:19 +0530 Subject: [PATCH 3/5] FDS-653 links in pdf working --- stories/flow-dashboard/f-dashboard.stories.ts | 200 +++++++++++++----- 1 file changed, 149 insertions(+), 51 deletions(-) diff --git a/stories/flow-dashboard/f-dashboard.stories.ts b/stories/flow-dashboard/f-dashboard.stories.ts index c77ffec63..c3cdfdc5a 100644 --- a/stories/flow-dashboard/f-dashboard.stories.ts +++ b/stories/flow-dashboard/f-dashboard.stories.ts @@ -29,7 +29,7 @@ const getWidgets = () => { ]; const widgets: FDashboardWidget[] = []; const startFrom = new Date(); - for (let index = 0; index < 10; index++) { + for (let index = 0; index < 20; index++) { if (index % 2 === 0) { widgets.push({ type: "timeseries", @@ -37,11 +37,43 @@ const getWidgets = () => { data: generateTimeseriesChartData(startFrom) }, id: faker.string.alpha(10), - header: { - title: faker.company.name(), - description: faker.lorem.sentences(3) + header() { + const name = faker.company.name(); + const description = faker.lorem.sentences(3); + return html` + + + ${name} + ${description} + + `; + }, + footer: () => { + const date = faker.date.recent({ refDate: new Date() }); + const state = faker.helpers.arrayElement(["danger", "success", "warning"]); + return html` + + + Last updated on ${date.toLocaleDateString()} ${date.toLocaleTimeString()} + + + `; }, - footer: `Powered by Flow`, placement: { w: faker.number.int({ min: 4, max: 8 }), h: faker.number.int({ min: 3, max: 4 }) @@ -87,61 +119,128 @@ const Template = () => { * * */ + const downloadFile = () => { + const element = document.querySelector("#dashboard-to-export") as FDashboard; + + html2canvas(element, { scale: 1 }).then(function (canvas) { + // Initialize jsPDF + const pdf = new jsPDF({ + orientation: "p", + unit: "px", + format: [element.scrollWidth, element.scrollHeight] + }); + pdf.setDisplayMode("original"); + + const allAnchors = element.querySelectorAll("a"); + allAnchors.forEach(anchorElement => { + console.log( + anchorElement, + anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, + anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y, + anchorElement.offsetWidth, + anchorElement.offsetHeight, + { url: anchorElement.href } + ); + pdf.link( + anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, + anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y, + anchorElement.offsetWidth, + anchorElement.offsetHeight, + { url: anchorElement.href } + ); + }); + + pdf.addImage(canvas, "PNG", 0, 0, element.scrollWidth, element.scrollHeight); + + // Save the PDF + pdf.save("canvas_to_pdf.pdf"); + }); + // html2canvas(element, { scale: 1 }).then(function (canvas) { + // const imgData = canvas.toDataURL("image/png"); + // const imgWidth = 210; + // const pageHeight = 250; + // const imgHeight = (canvas.height * imgWidth) / canvas.width; + // let heightLeft = imgHeight; + // const doc = new jsPDF({ + // orientation: "p", + // unit: "mm" + // }); + // let position = 0; // give some top padding to first page + // doc.link(0, 0, element.scrollWidth, 5, { url: "http://www.google.com" }); + // doc.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); + // heightLeft -= pageHeight; + + // while (heightLeft >= 0) { + // position += heightLeft - imgHeight; // top padding for other pages + // doc.addPage(); + // doc.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); + // heightLeft -= pageHeight; + // } + // doc.save("canvas_to_pdf.pdf"); + // }); + }; // const downloadFile = () => { // const element = document.querySelector("#dashboard-to-export") as FDashboard; // html2canvas(element, { scale: 1 }).then(function (canvas) { - // // Initialize jsPDF - // const pdf = new jsPDF({ - // orientation: "p", - // unit: "px", - // format: [element.scrollWidth, element.scrollHeight] - // }); - // pdf.setDisplayMode("original"); - // //const dataURL = canvas.toDataURL("image/png", 1.0); - // // Add canvas image to PDF - // //imgRef.value!.src = dataURL; - - // pdf.addImage(canvas, "PNG", 0, 0, element.scrollWidth, element.scrollHeight); - - // // Save the PDF - // pdf.save("canvas_to_pdf.pdf"); + // const opt = { + // enableLinks: true, + // html2canvas: { + // scale: 1, + // allowTaint: true + // }, + // jsPDF: { + // orientation: "p", + // unit: "px", + // format: [element.scrollWidth, element.scrollHeight] + // } + // }; + // html2pdf().set(opt).from(element).save(); // }); + // // New Promise-based usage: // }; - const downloadFile = () => { - // const allSVGS = document.querySelectorAll("svg"); - // console.log(allSVGS); - // for (let i = 0; i < allSVGS.length; i++) { - // console.log(allSVGS[i]); - // html2canvas(allSVGS.item(i) as unknown as HTMLElement, { scale: 1 }).then(function (canvas) { - // allSVGS[i].outerHTML = ``; - // }); - // } + // const downloadFile = () => { + // // const allSVGS = document.querySelectorAll("svg"); + // // console.log(allSVGS); + // // for (let i = 0; i < allSVGS.length; i++) { + // // console.log(allSVGS[i]); + // // html2canvas(allSVGS.item(i) as unknown as HTMLElement, { scale: 1 }).then(function (canvas) { + // // allSVGS[i].outerHTML = ``; + // // }); + // // } - const element = document.querySelector("#dashboard-to-export") as FDashboard; - const doc = new jsPDF({ - orientation: "p", - unit: "px", - format: [element.scrollWidth, element.scrollHeight] - }); + // const element = document.querySelector("#dashboard-to-export") as FDashboard; + // const doc = new jsPDF({ + // orientation: "p", + // unit: "px", + // format: [element.scrollWidth, element.scrollHeight] + // }); - doc.html(element, { - html2canvas: { - scale: 1, - async: true, - svgRendering: true - }, - callback: function (doc) { - doc.save("sample-document.pdf"); - }, - width: element.scrollWidth, - windowWidth: 2400 - }); - }; + // doc.html(element, { + // html2canvas: { + // scale: 1, + // async: true, + // svgRendering: true + // }, + // callback: function (doc) { + // console.log(doc); + // doc.save("sample-document.pdf"); + // }, + // width: element.scrollWidth, + // windowWidth: 2400 + // }); + // }; - return html` - + return html` + Ollion { From bd3efff36b106f77ca7920f8bb00dca803c4c044 Mon Sep 17 00:00:00 2001 From: vikas-cldcvr Date: Tue, 12 Mar 2024 13:57:44 +0530 Subject: [PATCH 4/5] FDS-653 text in pdf --- stories/flow-dashboard/f-dashboard.stories.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/stories/flow-dashboard/f-dashboard.stories.ts b/stories/flow-dashboard/f-dashboard.stories.ts index c3cdfdc5a..9a22de590 100644 --- a/stories/flow-dashboard/f-dashboard.stories.ts +++ b/stories/flow-dashboard/f-dashboard.stories.ts @@ -133,14 +133,6 @@ const Template = () => { const allAnchors = element.querySelectorAll("a"); allAnchors.forEach(anchorElement => { - console.log( - anchorElement, - anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, - anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y, - anchorElement.offsetWidth, - anchorElement.offsetHeight, - { url: anchorElement.href } - ); pdf.link( anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y, @@ -152,13 +144,21 @@ const Template = () => { pdf.addImage(canvas, "PNG", 0, 0, element.scrollWidth, element.scrollHeight); + // allAnchors.forEach(anchorElement => { + // pdf.text( + // anchorElement.innerText, + // anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, + // anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y + // ); + // }); + // Save the PDF pdf.save("canvas_to_pdf.pdf"); }); // html2canvas(element, { scale: 1 }).then(function (canvas) { // const imgData = canvas.toDataURL("image/png"); // const imgWidth = 210; - // const pageHeight = 250; + // const pageHeight = 297; // const imgHeight = (canvas.height * imgWidth) / canvas.width; // let heightLeft = imgHeight; // const doc = new jsPDF({ From 1ba4fe1a8cc1cbc42ecf08f6c17d0433ecb910ca Mon Sep 17 00:00:00 2001 From: vikas-cldcvr Date: Tue, 12 Mar 2024 18:30:32 +0530 Subject: [PATCH 5/5] FDS-653 pdf pagination --- stories/flow-dashboard/f-dashboard.stories.ts | 121 ++++-------------- 1 file changed, 28 insertions(+), 93 deletions(-) diff --git a/stories/flow-dashboard/f-dashboard.stories.ts b/stories/flow-dashboard/f-dashboard.stories.ts index 9a22de590..bf64c2cfe 100644 --- a/stories/flow-dashboard/f-dashboard.stories.ts +++ b/stories/flow-dashboard/f-dashboard.stories.ts @@ -100,6 +100,7 @@ const getWidgets = () => { return widgets; }; + const Template = () => { const dashboardRef = createRef(); const imgRef = createRef(); @@ -123,114 +124,48 @@ const Template = () => { const element = document.querySelector("#dashboard-to-export") as FDashboard; html2canvas(element, { scale: 1 }).then(function (canvas) { + const imgData = canvas.toDataURL("image/png"); + const imgWidth = 794; + const pageHeight = 1115; + const imgHeight = (canvas.height * imgWidth) / canvas.width; + let heightLeft = imgHeight; // Initialize jsPDF const pdf = new jsPDF({ orientation: "p", unit: "px", - format: [element.scrollWidth, element.scrollHeight] + format: [794, 1115] }); pdf.setDisplayMode("original"); + let position = 0; // give some top padding to first page const allAnchors = element.querySelectorAll("a"); - allAnchors.forEach(anchorElement => { - pdf.link( - anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, - anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y, - anchorElement.offsetWidth, - anchorElement.offsetHeight, - { url: anchorElement.href } - ); - }); + for (let l = 0; l < allAnchors.length; l++) { + const anchorElement = allAnchors.item(l); + const linkX = anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x; + const linkY = anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y; + const linkWidth = anchorElement.offsetWidth; + const linkHeight = anchorElement.offsetHeight; + + pdf.link(linkX, linkY, linkWidth, linkHeight, { + url: anchorElement.href + }); + } - pdf.addImage(canvas, "PNG", 0, 0, element.scrollWidth, element.scrollHeight); + pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); + heightLeft -= pageHeight; - // allAnchors.forEach(anchorElement => { - // pdf.text( - // anchorElement.innerText, - // anchorElement.getBoundingClientRect().x - element.getBoundingClientRect().x, - // anchorElement.getBoundingClientRect().y - element.getBoundingClientRect().y - // ); - // }); + while (heightLeft >= 0) { + position += heightLeft - imgHeight; // top padding for other pages + pdf.addPage(); + + pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); + heightLeft -= pageHeight; + } // Save the PDF pdf.save("canvas_to_pdf.pdf"); }); - // html2canvas(element, { scale: 1 }).then(function (canvas) { - // const imgData = canvas.toDataURL("image/png"); - // const imgWidth = 210; - // const pageHeight = 297; - // const imgHeight = (canvas.height * imgWidth) / canvas.width; - // let heightLeft = imgHeight; - // const doc = new jsPDF({ - // orientation: "p", - // unit: "mm" - // }); - // let position = 0; // give some top padding to first page - // doc.link(0, 0, element.scrollWidth, 5, { url: "http://www.google.com" }); - // doc.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); - // heightLeft -= pageHeight; - - // while (heightLeft >= 0) { - // position += heightLeft - imgHeight; // top padding for other pages - // doc.addPage(); - // doc.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); - // heightLeft -= pageHeight; - // } - // doc.save("canvas_to_pdf.pdf"); - // }); }; - // const downloadFile = () => { - // const element = document.querySelector("#dashboard-to-export") as FDashboard; - - // html2canvas(element, { scale: 1 }).then(function (canvas) { - // const opt = { - // enableLinks: true, - // html2canvas: { - // scale: 1, - // allowTaint: true - // }, - // jsPDF: { - // orientation: "p", - // unit: "px", - // format: [element.scrollWidth, element.scrollHeight] - // } - // }; - // html2pdf().set(opt).from(element).save(); - // }); - // // New Promise-based usage: - // }; - - // const downloadFile = () => { - // // const allSVGS = document.querySelectorAll("svg"); - // // console.log(allSVGS); - // // for (let i = 0; i < allSVGS.length; i++) { - // // console.log(allSVGS[i]); - // // html2canvas(allSVGS.item(i) as unknown as HTMLElement, { scale: 1 }).then(function (canvas) { - // // allSVGS[i].outerHTML = ``; - // // }); - // // } - - // const element = document.querySelector("#dashboard-to-export") as FDashboard; - // const doc = new jsPDF({ - // orientation: "p", - // unit: "px", - // format: [element.scrollWidth, element.scrollHeight] - // }); - - // doc.html(element, { - // html2canvas: { - // scale: 1, - // async: true, - // svgRendering: true - // }, - // callback: function (doc) { - // console.log(doc); - // doc.save("sample-document.pdf"); - // }, - // width: element.scrollWidth, - // windowWidth: 2400 - // }); - // }; return html`