From 719ef17501b81a68b9ba9c25f89ec18b473eb1f3 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Tue, 31 Mar 2020 20:53:25 +0530 Subject: [PATCH] integrated html2canvas and jsPDF to generate PDFs --- package-lock.json | 188 ++++++++++++++++++++ package.json | 4 +- src/components/App/App.js | 18 +- src/components/RightSidebar/RightSidebar.js | 2 +- src/components/RightSidebar/tabs/Actions.js | 40 ++++- src/context/PageContext.js | 23 +++ src/i18n/index.js | 3 +- src/index.css | 5 +- src/index.js | 5 +- src/templates/onyx/Onyx.js | 1 + 10 files changed, 272 insertions(+), 17 deletions(-) create mode 100644 src/context/PageContext.js diff --git a/package-lock.json b/package-lock.json index 9b7b2bd8..8f50a732 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4223,6 +4223,11 @@ } } }, + "base64-arraybuffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz", + "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==" + }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -4752,6 +4757,103 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001036.tgz", "integrity": "sha512-jU8CIFIj2oR7r4W+5AKcsvWNVIb6Q6OZE3UsrXrZBHFtreT4YgTeOJtTucp+zSedEpTi3L5wASSP0LYIE3if6w==" }, + "canvg": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-1.5.3.tgz", + "integrity": "sha512-7Gn2IuQzvUQWPIuZuFHrzsTM0gkPz2RRT9OcbdmA03jeKk8kltrD8gqUzNX15ghY/4PV5bbe5lmD6yDLDY6Ybg==", + "requires": { + "jsdom": "^8.1.0", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^1.4.1", + "xmldom": "^0.1.22" + }, + "dependencies": { + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=" + }, + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" + }, + "acorn-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", + "requires": { + "acorn": "^2.1.0" + } + }, + "cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "requires": { + "cssom": "0.3.x" + } + }, + "jsdom": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-8.5.0.tgz", + "integrity": "sha1-1Nj12/J2hjW2KmKCO5R89wcevJg=", + "requires": { + "abab": "^1.0.0", + "acorn": "^2.4.0", + "acorn-globals": "^1.0.4", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.0 < 0.4.0", + "cssstyle": ">= 0.2.34 < 0.3.0", + "escodegen": "^1.6.1", + "iconv-lite": "^0.4.13", + "nwmatcher": ">= 1.3.7 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.55.0", + "sax": "^1.1.4", + "symbol-tree": ">= 3.1.0 < 4.0.0", + "tough-cookie": "^2.2.0", + "webidl-conversions": "^3.0.1", + "whatwg-url": "^2.0.1", + "xml-name-validator": ">= 2.0.1 < 3.0.0" + } + }, + "parse5": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=" + }, + "stackblur-canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-1.4.1.tgz", + "integrity": "sha1-hJqm+UsnL/JvZHH6QTDtH35HlVs=" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-2.0.1.tgz", + "integrity": "sha1-U5ayBD8CDub3BNnEXqhRnnJN5lk=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=" + } + } + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -5605,6 +5707,14 @@ } } }, + "css-line-break": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.1.1.tgz", + "integrity": "sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA==", + "requires": { + "base64-arraybuffer": "^0.2.0" + } + }, "css-loader": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz", @@ -7485,6 +7595,10 @@ "schema-utils": "^2.5.0" } }, + "file-saver": { + "version": "github:eligrey/FileSaver.js#e865e37af9f9947ddcced76b549e27dc45c1cb2e", + "from": "github:eligrey/FileSaver.js#1.3.8" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -8406,6 +8520,14 @@ } } }, + "html2canvas": { + "version": "1.0.0-rc.5", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-rc.5.tgz", + "integrity": "sha512-DtNqPxJNXPoTajs+lVQzGS1SULRI4GQaROeU5R41xH8acffHukxRh/NBVcTBsfCkJSkLq91rih5TpbEwUP9yWA==", + "requires": { + "css-line-break": "1.1.1" + } + }, "htmlparser2": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", @@ -10282,6 +10404,42 @@ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" }, + "jspdf": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-1.5.3.tgz", + "integrity": "sha512-J9X76xnncMw+wIqb15HeWfPMqPwYxSpPY8yWPJ7rAZN/ZDzFkjCSZObryCyUe8zbrVRNiuCnIeQteCzMn7GnWw==", + "requires": { + "canvg": "1.5.3", + "file-saver": "github:eligrey/FileSaver.js#1.3.8", + "html2canvas": "1.0.0-alpha.12", + "omggif": "1.0.7", + "promise-polyfill": "8.1.0", + "stackblur-canvas": "2.2.0" + }, + "dependencies": { + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "css-line-break": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-1.0.1.tgz", + "integrity": "sha1-GfIGOjPpX7KDG4ZEbAuAwYivRQo=", + "requires": { + "base64-arraybuffer": "^0.1.5" + } + }, + "html2canvas": { + "version": "1.0.0-alpha.12", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz", + "integrity": "sha1-OxmS48mz9WBjw1/WIElPN+uohRM=", + "requires": { + "css-line-break": "1.0.1" + } + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -11383,6 +11541,11 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==" + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -11517,6 +11680,11 @@ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" }, + "omggif": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.7.tgz", + "integrity": "sha1-WdLuywJj3oRjWz/riHwMmXPx5J0=" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -13326,6 +13494,11 @@ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" }, + "promise-polyfill": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", + "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==" + }, "prompts": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", @@ -14548,6 +14721,11 @@ "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" }, + "rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha1-1lBezbMEplldom+ktDMHMGd1lF0=" + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -15334,6 +15512,11 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" }, + "stackblur-canvas": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.2.0.tgz", + "integrity": "sha512-5Gf8dtlf8k6NbLzuly2NkGrkS/Ahh+I5VUjO7TnFizdJtgpfpLLEdQlLe9umbcnZlitU84kfYjXE67xlSXfhfQ==" + }, "state-toggle": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", @@ -18861,6 +19044,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" + }, "xregexp": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", diff --git a/package.json b/package.json index e3d53791..71ca1f9c 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,17 @@ "@vuepress/plugin-google-analytics": "^1.4.0", "autoprefixer": "^9.7.5", "axios": "^0.19.2", + "html2canvas": "^1.0.0-rc.5", "i18next": "^19.3.4", + "jspdf": "^1.5.3", "lodash": "^4.17.15", "postcss-cli": "^7.1.0", + "react": "^16.13.1", "react-dom": "^16.13.1", "react-i18next": "^11.3.4", "react-markdown": "^4.3.1", "react-scripts": "3.4.1", "react-toastify": "^5.5.0", - "react": "^16.13.1", "tailwindcss": "^1.2.0", "uuid": "^7.0.2", "vuepress": "^1.4.0" diff --git a/src/components/App/App.js b/src/components/App/App.js index ff67870d..ce0a7d37 100644 --- a/src/components/App/App.js +++ b/src/components/App/App.js @@ -1,23 +1,31 @@ -import React, { useEffect, useContext, Suspense } from 'react'; +import React, { useRef, useEffect, useContext, Suspense } from 'react'; import { useTranslation } from 'react-i18next'; import AppContext from '../../context/AppContext'; +import PageContext from '../../context/PageContext'; + import LeftSidebar from '../LeftSidebar/LeftSidebar'; import RightSidebar from '../RightSidebar/RightSidebar'; import templates from '../../templates'; const App = () => { + const pageRef = useRef(null); const { i18n } = useTranslation(); + const context = useContext(AppContext); const { state, dispatch } = context; const { theme, settings } = state; + const pageContext = useContext(PageContext); + const { setPageElement } = pageContext; + useEffect(() => { + setPageElement(pageRef); i18n.changeLanguage(settings.language); const storedState = JSON.parse(localStorage.getItem('state')); dispatch({ type: 'import_data', payload: storedState }); - }, [dispatch, i18n, settings.language]); + }, [dispatch, setPageElement, i18n, settings.language]); return ( @@ -25,11 +33,7 @@ const App = () => {
-
+
{templates.find(x => theme.layout.toLowerCase() === x.key).component()}
diff --git a/src/components/RightSidebar/RightSidebar.js b/src/components/RightSidebar/RightSidebar.js index 8c2eebb5..024ca966 100644 --- a/src/components/RightSidebar/RightSidebar.js +++ b/src/components/RightSidebar/RightSidebar.js @@ -43,7 +43,7 @@ const RightSidebar = () => { name: t('about.title'), }, ]; - const [currentTab, setCurrentTab] = useState(tabs[0].key); + const [currentTab, setCurrentTab] = useState(tabs[3].key); const onChange = (key, value) => { dispatch({ diff --git a/src/components/RightSidebar/tabs/Actions.js b/src/components/RightSidebar/tabs/Actions.js index afaa0a3f..361c91f4 100644 --- a/src/components/RightSidebar/tabs/Actions.js +++ b/src/components/RightSidebar/tabs/Actions.js @@ -1,9 +1,16 @@ +/* eslint-disable new-cap */ /* eslint-disable jsx-a11y/anchor-has-content */ /* eslint-disable jsx-a11y/anchor-is-valid */ -import React, { useRef } from 'react'; +import React, { useRef, useContext } from 'react'; import { useTranslation, Trans } from 'react-i18next'; +import html2canvas from 'html2canvas'; +import * as jsPDF from 'jspdf'; + +import PageContext from '../../../context/PageContext'; const ActionsTab = ({ data, theme, dispatch }) => { + const pageContext = useContext(PageContext); + const { pageElement } = pageContext; const { t } = useTranslation('rightSidebar'); const fileInputRef = useRef(null); @@ -17,6 +24,35 @@ const ActionsTab = ({ data, theme, dispatch }) => { fr.readAsText(event.target.files[0]); }; + const printAsPdf = () => { + pageElement.current.style.maxHeight = 'fit-content'; + pageElement.current.style.overflow = 'visible'; + html2canvas(pageElement.current, { + useCORS: true, + allowTaint: true, + }).then(canvas => { + pageElement.current.style.maxHeight = '29.7cm'; + pageElement.current.style.overflow = 'scroll'; + const image = canvas.toDataURL('image/jpeg', 1.0); + const doc = new jsPDF('p', 'px', 'a4'); + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + + const widthRatio = pageWidth / canvas.width; + const heightRatio = pageHeight / canvas.height; + const ratio = widthRatio > heightRatio ? heightRatio : widthRatio; + + const canvasWidth = canvas.width * ratio; + const canvasHeight = canvas.height * ratio; + + const marginX = (pageWidth - canvasWidth) / 2; + const marginY = (pageHeight - canvasHeight) / 2; + + doc.addImage(image, 'JPEG', marginX, marginY, canvasWidth, canvasHeight); + doc.output('dataurlnewwindow'); + }); + }; + const exportToJson = () => { const backupObj = { data, theme }; const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(backupObj))}`; @@ -90,7 +126,7 @@ const ActionsTab = ({ data, theme, dispatch }) => {