mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-15 09:11:57 +10:00
integrated html2canvas and jsPDF to generate PDFs
This commit is contained in:
@ -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 (
|
||||
<Suspense fallback="Loading...">
|
||||
@ -25,11 +33,7 @@ const App = () => {
|
||||
<LeftSidebar />
|
||||
|
||||
<div className="z-0 h-screen col-span-3 flex justify-center items-center overflow-scroll">
|
||||
<div
|
||||
id="page"
|
||||
className="animated fadeIn my-auto shadow-2xl"
|
||||
style={{ animationDelay: '500ms' }}
|
||||
>
|
||||
<div id="page" ref={pageRef} className="my-auto shadow-2xl">
|
||||
{templates.find(x => theme.layout.toLowerCase() === x.key).component()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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 }) => {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => window.print()}
|
||||
onClick={printAsPdf}
|
||||
className="mt-4 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium py-2 px-5 rounded"
|
||||
>
|
||||
<div className="flex justify-center items-center">
|
||||
|
||||
23
src/context/PageContext.js
Normal file
23
src/context/PageContext.js
Normal file
@ -0,0 +1,23 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const PageContext = React.createContext(null);
|
||||
const { Provider } = PageContext;
|
||||
|
||||
const StateProvider = ({ children }) => {
|
||||
const [pageElement, setPageElement] = useState(null);
|
||||
return (
|
||||
<Provider
|
||||
value={{
|
||||
pageElement,
|
||||
setPageElement,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageProvider = StateProvider;
|
||||
export const PageConsumer = PageContext.Consumer;
|
||||
|
||||
export default PageContext;
|
||||
@ -23,10 +23,9 @@ const languages = [
|
||||
];
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: 'en',
|
||||
fallbackLng: 'en',
|
||||
resources,
|
||||
debug: true,
|
||||
ns: ['app', 'leftSidebar', 'rightSidebar'],
|
||||
defaultNS: 'app',
|
||||
});
|
||||
|
||||
@ -66,8 +66,8 @@ ul li {
|
||||
|
||||
#page {
|
||||
width: 21cm;
|
||||
height: 29.7cm;
|
||||
padding: 2.5em;
|
||||
min-height: 29.7cm;
|
||||
max-height: 29.7cm;
|
||||
zoom: 0.8;
|
||||
overflow: scroll;
|
||||
background-color: white;
|
||||
@ -90,7 +90,6 @@ ul li {
|
||||
|
||||
#page,
|
||||
#page * {
|
||||
page-break-inside: avoid;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import './index.css';
|
||||
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import { AppProvider } from './context/AppContext';
|
||||
import { PageProvider } from './context/PageContext';
|
||||
import App from './components/App/App';
|
||||
|
||||
toast.configure({
|
||||
@ -21,7 +22,9 @@ toast.configure({
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<AppProvider>
|
||||
<App />
|
||||
<PageProvider>
|
||||
<App />
|
||||
</PageProvider>
|
||||
</AppProvider>
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root'),
|
||||
|
||||
@ -230,6 +230,7 @@ const Onyx = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="p-10"
|
||||
style={{
|
||||
fontFamily: theme.font.family,
|
||||
backgroundColor: theme.colors.background,
|
||||
|
||||
Reference in New Issue
Block a user