mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-14 08:42:08 +10:00
introduce print dialog to add more options in exporting resume
This commit is contained in:
@ -10,6 +10,7 @@ import RightSidebar from '../RightSidebar/RightSidebar';
|
||||
|
||||
import templates from '../../templates';
|
||||
import PageController from '../../shared/PageController';
|
||||
import PrintDialog from '../../shared/PrintDialog';
|
||||
|
||||
const App = () => {
|
||||
const pageRef = useRef(null);
|
||||
@ -36,7 +37,7 @@ const App = () => {
|
||||
<div className="h-screen grid grid-cols-5 items-center">
|
||||
<LeftSidebar />
|
||||
|
||||
<div className="relative z-0 h-screen overflow-hidden col-span-3 flex justify-center items-center">
|
||||
<div className="relative z-10 h-screen overflow-hidden col-span-3 flex justify-center items-center">
|
||||
<PanZoom
|
||||
ref={panZoomRef}
|
||||
minZoom="0.4"
|
||||
@ -48,7 +49,7 @@ const App = () => {
|
||||
style={{ outline: 'none' }}
|
||||
>
|
||||
<div id="page" ref={pageRef} className="shadow-2xl break-words">
|
||||
{templates.find(x => theme.layout.toLowerCase() === x.key).component()}
|
||||
{templates.find((x) => theme.layout.toLowerCase() === x.key).component()}
|
||||
</div>
|
||||
</PanZoom>
|
||||
|
||||
@ -56,6 +57,8 @@ const App = () => {
|
||||
</div>
|
||||
|
||||
<RightSidebar />
|
||||
|
||||
<PrintDialog />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
/* eslint-disable new-cap */
|
||||
/* eslint-disable jsx-a11y/anchor-has-content */
|
||||
/* eslint-disable jsx-a11y/anchor-is-valid */
|
||||
|
||||
import React, { useRef, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import PageContext from '../../../context/PageContext';
|
||||
import { importJson, saveAsPdf } from '../../../utils';
|
||||
import { importJson } from '../../../utils';
|
||||
|
||||
const ActionsTab = ({ data, theme, dispatch }) => {
|
||||
const pageContext = useContext(PageContext);
|
||||
const { pageRef, panZoomRef } = pageContext;
|
||||
const { setPrintDialogOpen } = pageContext;
|
||||
const { t } = useTranslation('rightSidebar');
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
@ -47,7 +48,7 @@ const ActionsTab = ({ data, theme, dispatch }) => {
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={e => importJson(e, dispatch)}
|
||||
onChange={(e) => importJson(e, dispatch)}
|
||||
/>
|
||||
<a id="downloadAnchor" className="hidden" />
|
||||
|
||||
@ -84,7 +85,7 @@ const ActionsTab = ({ data, theme, dispatch }) => {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => saveAsPdf(pageRef, panZoomRef)}
|
||||
onClick={() => setPrintDialogOpen(true)}
|
||||
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">
|
||||
|
||||
@ -97,7 +97,7 @@ const reducer = (state, { type, payload }) => {
|
||||
return set({ ...state }, `data.${payload.key}.items`, items);
|
||||
case 'delete_item':
|
||||
items = get({ ...state }, `data.${payload.key}.items`, []);
|
||||
remove(items, x => x === payload.value);
|
||||
remove(items, (x) => x === payload.value);
|
||||
return set({ ...state }, `data.${payload.key}.items`, items);
|
||||
case 'move_item_up':
|
||||
items = get({ ...state }, `data.${payload.key}.items`, []);
|
||||
|
||||
@ -4,8 +4,9 @@ const PageContext = React.createContext(null);
|
||||
const { Provider } = PageContext;
|
||||
|
||||
const StateProvider = ({ children }) => {
|
||||
const [panZoomRef, setPanZoomRef] = useState(null);
|
||||
const [pageRef, setPageRef] = useState(null);
|
||||
const [panZoomRef, setPanZoomRef] = useState(null);
|
||||
const [isPrintDialogOpen, setPrintDialogOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Provider
|
||||
@ -14,6 +15,8 @@ const StateProvider = ({ children }) => {
|
||||
setPageRef,
|
||||
panZoomRef,
|
||||
setPanZoomRef,
|
||||
isPrintDialogOpen,
|
||||
setPrintDialogOpen,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -16,5 +16,23 @@
|
||||
"label": "Add"
|
||||
}
|
||||
},
|
||||
"printDialog": {
|
||||
"heading": "Download your Resume",
|
||||
"quality": {
|
||||
"label": "Quality"
|
||||
},
|
||||
"printType": {
|
||||
"label": "Type",
|
||||
"types": {
|
||||
"unconstrained": "Unconstrained",
|
||||
"fitInA4": "Fit in A4",
|
||||
"multiPageA4": "Multi-Page A4"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"cancel": "Cancel",
|
||||
"saveAsPdf": "Save as PDF"
|
||||
}
|
||||
},
|
||||
"markdownHelpText": "You can use <1>GitHub Flavored Markdown</1> to style this section of the text."
|
||||
}
|
||||
|
||||
@ -32,6 +32,35 @@ ul li {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type='range']::-moz-range-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
appearance: none;
|
||||
cursor: ew-resize;
|
||||
background: #fff;
|
||||
border: none;
|
||||
box-shadow: -405px 0 0 400px #605e5c;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input[type='range']::-webkit-slider-thumb {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
appearance: none;
|
||||
cursor: ew-resize;
|
||||
background: #fff;
|
||||
border: none;
|
||||
box-shadow: -405px 0 0 400px #605e5c;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.centered {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media screen {
|
||||
input[type='number']::-webkit-inner-spin-button,
|
||||
input[type='number']::-webkit-outer-spin-button {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import PageContext from '../context/PageContext';
|
||||
import { saveAsPdf } from '../utils';
|
||||
|
||||
const PageController = () => {
|
||||
const pageContext = useContext(PageContext);
|
||||
const { pageRef, panZoomRef } = pageContext;
|
||||
const { panZoomRef, setPrintDialogOpen } = pageContext;
|
||||
|
||||
const zoomIn = () => panZoomRef.current.zoomIn(2);
|
||||
const zoomOut = () => panZoomRef.current.zoomOut(2);
|
||||
@ -35,7 +35,7 @@ const PageController = () => {
|
||||
|
||||
<div
|
||||
className="p-3 hover:bg-gray-200 cursor-pointer flex"
|
||||
onClick={() => saveAsPdf(pageRef, panZoomRef)}
|
||||
onClick={() => setPrintDialogOpen(true)}
|
||||
>
|
||||
<i className="material-icons">save</i>
|
||||
</div>
|
||||
|
||||
103
src/shared/PrintDialog.js
Normal file
103
src/shared/PrintDialog.js
Normal file
@ -0,0 +1,103 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import PageContext from '../context/PageContext';
|
||||
import Dropdown from './Dropdown';
|
||||
import { saveAsPdf, saveAsMultiPagePdf } from '../utils';
|
||||
|
||||
const PrintDialog = () => {
|
||||
const { t } = useTranslation();
|
||||
const pageContext = useContext(PageContext);
|
||||
const { pageRef, panZoomRef, isPrintDialogOpen, setPrintDialogOpen } = pageContext;
|
||||
|
||||
const printTypes = [
|
||||
{ key: 'unconstrained', value: `${t('printDialog.printType.types.unconstrained')}` },
|
||||
{ key: 'fitInA4', value: `${t('printDialog.printType.types.fitInA4')}` },
|
||||
{ key: 'multiPageA4', value: `${t('printDialog.printType.types.multiPageA4')}` },
|
||||
];
|
||||
|
||||
const [quality, setQuality] = useState(80);
|
||||
const [type, setType] = useState(printTypes[0].key);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 transition-all duration-200 ease-in-out ${
|
||||
isPrintDialogOpen ? 'opacity-100 z-20' : 'opacity-0 z-0'
|
||||
}`}
|
||||
style={{ backgroundColor: 'rgba(0, 0, 0, 0.25)' }}
|
||||
onClick={() => {
|
||||
setPrintDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="centered py-8 px-12 bg-white shadow-xl rounded w-full md:w-1/3"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<h5 className="mb-6 text-lg font-bold">{t('printDialog.heading')}</h5>
|
||||
|
||||
<h6 className="mb-1 text-sm font-medium">{t('printDialog.quality.label')}</h6>
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="range"
|
||||
className="w-full h-4 my-2 rounded-full overflow-hidden appearance-none focus:outline-none bg-gray-400"
|
||||
value={quality}
|
||||
onChange={(e) => setQuality(e.target.value)}
|
||||
min="40"
|
||||
max="100"
|
||||
step="5"
|
||||
/>
|
||||
|
||||
<h6 className="font-medium pl-5">{quality}%</h6>
|
||||
</div>
|
||||
|
||||
<h6 className="mt-4 mb-2 text-sm font-medium">{t('printDialog.printType.label')}</h6>
|
||||
<Dropdown
|
||||
value={type}
|
||||
options={printTypes}
|
||||
onChange={setType}
|
||||
optionItem={(x) => (
|
||||
<option key={x.key} value={x.key}>
|
||||
{x.value}
|
||||
</option>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setPrintDialogOpen(false);
|
||||
}}
|
||||
className="mt-6 border border-red-600 text-red-600 hover:bg-red-600 hover:text-white text-sm font-medium py-2 px-5 rounded"
|
||||
>
|
||||
<div className="flex justify-center items-center">
|
||||
<i className="material-icons mr-2 font-bold text-base">close</i>
|
||||
<span className="text-sm">{t('printDialog.buttons.cancel')}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
await (type === 'multiPageA4'
|
||||
? saveAsMultiPagePdf(pageRef, panZoomRef, quality)
|
||||
: saveAsPdf(pageRef, panZoomRef, quality, type));
|
||||
setPrintDialogOpen(false);
|
||||
}}
|
||||
className="mt-6 border border-gray-700 text-gray-700 hover:bg-gray-700 hover:text-white text-sm font-medium py-2 px-5 rounded"
|
||||
>
|
||||
<div className="flex justify-center items-center">
|
||||
<i className="material-icons mr-2 font-bold text-base">save</i>
|
||||
<span className="text-sm">{t('printDialog.buttons.saveAsPdf')}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintDialog;
|
||||
@ -78,7 +78,7 @@ const Castform = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const SkillItem = x => (
|
||||
const SkillItem = (x) => (
|
||||
<li key={x} className="text-sm my-2">
|
||||
{x}
|
||||
</li>
|
||||
@ -96,7 +96,7 @@ const Castform = () => {
|
||||
const Objective = () =>
|
||||
data.objective && data.objective.enable && <p className="m-5 text-sm">{data.objective.body}</p>;
|
||||
|
||||
const WorkItem = x => (
|
||||
const WorkItem = (x) => (
|
||||
<div key={x.title} className="my-3 px-5">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
@ -116,11 +116,11 @@ const Castform = () => {
|
||||
data.work.enable && (
|
||||
<div>
|
||||
<Heading light title={data.work.heading} />
|
||||
{data.work.items.filter(x => x.enable).map(WorkItem)}
|
||||
{data.work.items.filter((x) => x.enable).map(WorkItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const ReferenceItem = x => (
|
||||
const ReferenceItem = (x) => (
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="text-sm font-medium">{x.name}</h6>
|
||||
<span className="text-xs">{x.position}</span>
|
||||
@ -136,12 +136,12 @@ const Castform = () => {
|
||||
<div>
|
||||
<Heading light title={data.references.heading} />
|
||||
<div className="grid grid-cols-2 gap-6 px-5">
|
||||
{data.references.items.filter(x => x.enable).map(ReferenceItem)}
|
||||
{data.references.items.filter((x) => x.enable).map(ReferenceItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LanguageItem = x => (
|
||||
const LanguageItem = (x) => (
|
||||
<div key={x.id} className="flex flex-col my-2">
|
||||
<h6 className="text-sm font-medium mb-1">{x.key}</h6>
|
||||
<div className="relative h-5">
|
||||
@ -168,12 +168,12 @@ const Castform = () => {
|
||||
<div>
|
||||
<Heading title={data.languages.heading} />
|
||||
<div className="px-5 mb-6">
|
||||
{data.languages.items.filter(x => x.enable).map(LanguageItem)}
|
||||
{data.languages.items.filter((x) => x.enable).map(LanguageItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const EducationItem = x => (
|
||||
const EducationItem = (x) => (
|
||||
<div key={x.name} className="my-3 px-5">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
@ -196,11 +196,11 @@ const Castform = () => {
|
||||
data.education.enable && (
|
||||
<div>
|
||||
<Heading light title={data.education.heading} />
|
||||
{data.education.items.filter(x => x.enable).map(EducationItem)}
|
||||
{data.education.items.filter((x) => x.enable).map(EducationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const AwardItem = x => (
|
||||
const AwardItem = (x) => (
|
||||
<div key={x.title} className="my-3 px-5">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -213,11 +213,11 @@ const Castform = () => {
|
||||
data.awards.enable && (
|
||||
<div>
|
||||
<Heading light title={data.awards.heading} />
|
||||
{data.awards.items.filter(x => x.enable).map(AwardItem)}
|
||||
{data.awards.items.filter((x) => x.enable).map(AwardItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const CertificationItem = x => (
|
||||
const CertificationItem = (x) => (
|
||||
<div key={x.title} className="my-3 px-5">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -230,11 +230,11 @@ const Castform = () => {
|
||||
data.certifications.enable && (
|
||||
<div>
|
||||
<Heading title={data.certifications.heading} />
|
||||
{data.certifications.items.filter(x => x.enable).map(CertificationItem)}
|
||||
{data.certifications.items.filter((x) => x.enable).map(CertificationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const ExtraItem = x => (
|
||||
const ExtraItem = (x) => (
|
||||
<div key={x.id} className="px-5 my-2">
|
||||
<h6 className="text-xs font-bold">{x.key}</h6>
|
||||
<div className="text-sm">{x.value}</div>
|
||||
@ -246,7 +246,7 @@ const Castform = () => {
|
||||
data.extras.enable && (
|
||||
<div>
|
||||
<Heading title={data.extras.heading} />
|
||||
{data.extras.items.filter(x => x.enable).map(ExtraItem)}
|
||||
{data.extras.items.filter((x) => x.enable).map(ExtraItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -260,7 +260,7 @@ const Castform = () => {
|
||||
>
|
||||
<div className="grid grid-cols-12">
|
||||
<div
|
||||
className="col-span-4 rounded"
|
||||
className="col-span-4"
|
||||
style={{
|
||||
color: theme.colors.background,
|
||||
backgroundColor: theme.colors.accent,
|
||||
|
||||
@ -63,7 +63,7 @@ const Gengar = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const SkillItem = x => (
|
||||
const SkillItem = (x) => (
|
||||
<li key={x} className="text-sm py-1">
|
||||
{x}
|
||||
</li>
|
||||
@ -78,7 +78,7 @@ const Gengar = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const EducationItem = x => (
|
||||
const EducationItem = (x) => (
|
||||
<div key={x.name} className="mb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -109,11 +109,11 @@ const Gengar = () => {
|
||||
data.education.enable && (
|
||||
<div className="mb-6">
|
||||
<Heading title={data.education.heading} />
|
||||
{data.education.items.filter(x => x.enable).map(EducationItem)}
|
||||
{data.education.items.filter((x) => x.enable).map(EducationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const CertificationItem = x => (
|
||||
const CertificationItem = (x) => (
|
||||
<div key={x.title} className="mb-3">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -126,11 +126,11 @@ const Gengar = () => {
|
||||
data.certifications.enable && (
|
||||
<div className="mb-6">
|
||||
<Heading title={data.certifications.heading} />
|
||||
{data.certifications.items.filter(x => x.enable).map(CertificationItem)}
|
||||
{data.certifications.items.filter((x) => x.enable).map(CertificationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const AwardItem = x => (
|
||||
const AwardItem = (x) => (
|
||||
<div key={x.title} className="mb-3">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -143,11 +143,11 @@ const Gengar = () => {
|
||||
data.awards.enable && (
|
||||
<div className="mb-6">
|
||||
<Heading title={data.awards.heading} />
|
||||
{data.awards.items.filter(x => x.enable).map(AwardItem)}
|
||||
{data.awards.items.filter((x) => x.enable).map(AwardItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const ReferenceItem = x => (
|
||||
const ReferenceItem = (x) => (
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="text-sm font-medium">{x.name}</h6>
|
||||
<span className="text-xs">{x.position}</span>
|
||||
@ -163,12 +163,12 @@ const Gengar = () => {
|
||||
<div>
|
||||
<Heading title={data.references.heading} />
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{data.references.items.filter(x => x.enable).map(ReferenceItem)}
|
||||
{data.references.items.filter((x) => x.enable).map(ReferenceItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const WorkItem = x => (
|
||||
const WorkItem = (x) => (
|
||||
<div key={x.title} className="mb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -188,11 +188,11 @@ const Gengar = () => {
|
||||
data.work.enable && (
|
||||
<div className="mb-6">
|
||||
<Heading title={data.work.heading} />
|
||||
{data.work.items.filter(x => x.enable).map(WorkItem)}
|
||||
{data.work.items.filter((x) => x.enable).map(WorkItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const LanguageItem = x => (
|
||||
const LanguageItem = (x) => (
|
||||
<div key={x.id} className="grid grid-cols-2 items-center py-2">
|
||||
<h6 className="text-sm font-medium">{x.key}</h6>
|
||||
<div className="flex">
|
||||
@ -210,11 +210,11 @@ const Gengar = () => {
|
||||
data.languages.enable && (
|
||||
<div>
|
||||
<Heading title={data.languages.heading} />
|
||||
<div className="mb-6">{data.languages.items.filter(x => x.enable).map(LanguageItem)}</div>
|
||||
<div className="mb-6">{data.languages.items.filter((x) => x.enable).map(LanguageItem)}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ExtraItem = x => (
|
||||
const ExtraItem = (x) => (
|
||||
<div key={x.id} className="text-sm my-1">
|
||||
<h6 className="text-xs font-bold">{x.key}</h6>
|
||||
<h6>{x.value}</h6>
|
||||
@ -227,7 +227,7 @@ const Gengar = () => {
|
||||
<div>
|
||||
<Heading title={data.extras.heading} />
|
||||
<div className="grid grid-cols-2">
|
||||
{data.extras.items.filter(x => x.enable).map(ExtraItem)}
|
||||
{data.extras.items.filter((x) => x.enable).map(ExtraItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -95,7 +95,7 @@ const Glalie = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const WorkItem = x => (
|
||||
const WorkItem = (x) => (
|
||||
<div key={x.title} className="mt-3">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
@ -114,11 +114,11 @@ const Glalie = () => {
|
||||
data.work.enable && (
|
||||
<div>
|
||||
<Heading title={data.work.heading} />
|
||||
{data.work.items.filter(x => x.enable).map(WorkItem)}
|
||||
{data.work.items.filter((x) => x.enable).map(WorkItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const EducationItem = x => (
|
||||
const EducationItem = (x) => (
|
||||
<div key={x.name} className="mt-3">
|
||||
<div>
|
||||
<h6 className="font-semibold text-xs">{x.name}</h6>
|
||||
@ -137,12 +137,12 @@ const Glalie = () => {
|
||||
<div>
|
||||
<Heading title={data.education.heading} />
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{data.education.items.filter(x => x.enable).map(EducationItem)}
|
||||
{data.education.items.filter((x) => x.enable).map(EducationItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AwardItem = x => (
|
||||
const AwardItem = (x) => (
|
||||
<div key={x.title} className="mt-3 text-left">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -155,11 +155,11 @@ const Glalie = () => {
|
||||
data.awards.enable && (
|
||||
<div>
|
||||
<Heading title={data.awards.heading} />
|
||||
{data.awards.items.filter(x => x.enable).map(AwardItem)}
|
||||
{data.awards.items.filter((x) => x.enable).map(AwardItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const CertificationItem = x => (
|
||||
const CertificationItem = (x) => (
|
||||
<div key={x.title} className="mt-3 text-left">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -172,11 +172,11 @@ const Glalie = () => {
|
||||
data.certifications.enable && (
|
||||
<div>
|
||||
<Heading title={data.certifications.heading} />
|
||||
{data.certifications.items.filter(x => x.enable).map(CertificationItem)}
|
||||
{data.certifications.items.filter((x) => x.enable).map(CertificationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const SkillItem = x => (
|
||||
const SkillItem = (x) => (
|
||||
<li key={x} className="text-xs font-medium">
|
||||
{x}
|
||||
</li>
|
||||
@ -191,7 +191,7 @@ const Glalie = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const LanguageItem = x => (
|
||||
const LanguageItem = (x) => (
|
||||
<div key={x.id} className="grid grid-cols-2 items-center py-2">
|
||||
<h6 className="text-xs font-medium text-left">{x.key}</h6>
|
||||
<div className="flex">
|
||||
@ -209,11 +209,13 @@ const Glalie = () => {
|
||||
data.languages.enable && (
|
||||
<div>
|
||||
<Heading title={data.languages.heading} />
|
||||
<div className="w-3/4">{data.languages.items.filter(x => x.enable).map(LanguageItem)}</div>
|
||||
<div className="w-3/4">
|
||||
{data.languages.items.filter((x) => x.enable).map(LanguageItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ReferenceItem = x => (
|
||||
const ReferenceItem = (x) => (
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="text-sm font-medium">{x.name}</h6>
|
||||
<span className="text-xs">{x.position}</span>
|
||||
@ -229,12 +231,12 @@ const Glalie = () => {
|
||||
<div>
|
||||
<Heading title={data.references.heading} />
|
||||
<div className="grid grid-cols-3 gap-8">
|
||||
{data.references.items.filter(x => x.enable).map(ReferenceItem)}
|
||||
{data.references.items.filter((x) => x.enable).map(ReferenceItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ExtraItem = x => (
|
||||
const ExtraItem = (x) => (
|
||||
<tr key={x.id}>
|
||||
<td className="border font-medium px-4 py-2 text-xs">{x.key}</td>
|
||||
<td className="border px-4 py-2 text-xs">{x.value}</td>
|
||||
@ -247,7 +249,7 @@ const Glalie = () => {
|
||||
<div>
|
||||
<Heading title={data.extras.heading} />
|
||||
<table className="mt-4 w-2/3 table-auto">
|
||||
<tbody>{data.extras.items.filter(x => x.enable).map(ExtraItem)}</tbody>
|
||||
<tbody>{data.extras.items.filter((x) => x.enable).map(ExtraItem)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -60,7 +60,7 @@ const Onyx = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const WorkItem = x => (
|
||||
const WorkItem = (x) => (
|
||||
<div key={x.title} className="mt-3">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
@ -80,11 +80,11 @@ const Onyx = () => {
|
||||
data.work.enable && (
|
||||
<div>
|
||||
<Heading title={data.work.heading} />
|
||||
{data.work.items.filter(x => x.enable).map(WorkItem)}
|
||||
{data.work.items.filter((x) => x.enable).map(WorkItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const EducationItem = x => (
|
||||
const EducationItem = (x) => (
|
||||
<div key={x.name} className="mt-3">
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
@ -107,11 +107,11 @@ const Onyx = () => {
|
||||
data.education.enable && (
|
||||
<div>
|
||||
<Heading title={data.education.heading} />
|
||||
{data.education.items.filter(x => x.enable).map(EducationItem)}
|
||||
{data.education.items.filter((x) => x.enable).map(EducationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const AwardItem = x => (
|
||||
const AwardItem = (x) => (
|
||||
<div key={x.title} className="mt-3">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -124,11 +124,11 @@ const Onyx = () => {
|
||||
data.awards.enable && (
|
||||
<div>
|
||||
<Heading title={data.awards.heading} />
|
||||
{data.awards.items.filter(x => x.enable).map(AwardItem)}
|
||||
{data.awards.items.filter((x) => x.enable).map(AwardItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const CertificationItem = x => (
|
||||
const CertificationItem = (x) => (
|
||||
<div key={x.title} className="mt-3">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -141,11 +141,11 @@ const Onyx = () => {
|
||||
data.certifications.enable && (
|
||||
<div>
|
||||
<Heading title={data.certifications.heading} />
|
||||
{data.certifications.items.filter(x => x.enable).map(CertificationItem)}
|
||||
{data.certifications.items.filter((x) => x.enable).map(CertificationItem)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const SkillItem = x => (
|
||||
const SkillItem = (x) => (
|
||||
<span
|
||||
key={x}
|
||||
className="text-xs rounded-full px-3 py-1 font-medium my-2 mr-2"
|
||||
@ -167,7 +167,7 @@ const Onyx = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const LanguageItem = x => (
|
||||
const LanguageItem = (x) => (
|
||||
<div key={x.id} className="grid grid-cols-2 items-center py-2">
|
||||
<h6 className="text-sm font-medium">{x.key}</h6>
|
||||
<div className="flex">
|
||||
@ -185,11 +185,13 @@ const Onyx = () => {
|
||||
data.languages.enable && (
|
||||
<div>
|
||||
<Heading title={data.languages.heading} />
|
||||
<div className="w-3/4">{data.languages.items.filter(x => x.enable).map(LanguageItem)}</div>
|
||||
<div className="w-3/4">
|
||||
{data.languages.items.filter((x) => x.enable).map(LanguageItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ReferenceItem = x => (
|
||||
const ReferenceItem = (x) => (
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="text-sm font-medium">{x.name}</h6>
|
||||
<span className="text-xs">{x.position}</span>
|
||||
@ -205,12 +207,12 @@ const Onyx = () => {
|
||||
<div>
|
||||
<Heading title={data.references.heading} />
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
{data.references.items.filter(x => x.enable).map(ReferenceItem)}
|
||||
{data.references.items.filter((x) => x.enable).map(ReferenceItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ExtraItem = x => (
|
||||
const ExtraItem = (x) => (
|
||||
<tr key={x.id}>
|
||||
<td className="border font-medium px-4 py-2 text-sm">{x.key}</td>
|
||||
<td className="border px-4 py-2 text-sm">{x.value}</td>
|
||||
@ -223,7 +225,7 @@ const Onyx = () => {
|
||||
<div>
|
||||
<Heading title={data.extras.heading} />
|
||||
<table className="w-2/3 table-auto">
|
||||
<tbody>{data.extras.items.filter(x => x.enable).map(ExtraItem)}</tbody>
|
||||
<tbody>{data.extras.items.filter((x) => x.enable).map(ExtraItem)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -58,7 +58,7 @@ const Pikachu = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const SkillItem = x => (
|
||||
const SkillItem = (x) => (
|
||||
<span
|
||||
key={x}
|
||||
className="leading-none rounded-lg text-sm font-medium bg-gray-300 py-3 my-1 px-4"
|
||||
@ -76,7 +76,7 @@ const Pikachu = () => {
|
||||
</div>
|
||||
);
|
||||
|
||||
const ReferenceItem = x => (
|
||||
const ReferenceItem = (x) => (
|
||||
<div key={x.id} className="flex flex-col">
|
||||
<h6 className="text-sm font-medium">{x.name}</h6>
|
||||
<span className="text-xs">{x.position}</span>
|
||||
@ -92,12 +92,12 @@ const Pikachu = () => {
|
||||
<div>
|
||||
<Heading title={data.references.heading} />
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
{data.references.items.filter(x => x.enable).map(ReferenceItem)}
|
||||
{data.references.items.filter((x) => x.enable).map(ReferenceItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const LanguageItem = x => (
|
||||
const LanguageItem = (x) => (
|
||||
<div key={x.id} className="grid grid-cols-2 items-center py-2">
|
||||
<h6 className="text-sm font-medium">{x.key}</h6>
|
||||
<div className="flex">
|
||||
@ -115,11 +115,11 @@ const Pikachu = () => {
|
||||
data.languages.enable && (
|
||||
<div>
|
||||
<Heading title={data.languages.heading} />
|
||||
<div className="mb-6">{data.languages.items.filter(x => x.enable).map(LanguageItem)}</div>
|
||||
<div className="mb-6">{data.languages.items.filter((x) => x.enable).map(LanguageItem)}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ExtraItem = x => (
|
||||
const ExtraItem = (x) => (
|
||||
<div key={x.id} className="text-sm my-1">
|
||||
<h6 className="text-xs font-bold">{x.key}</h6>
|
||||
<h6 className="">{x.value}</h6>
|
||||
@ -132,12 +132,12 @@ const Pikachu = () => {
|
||||
<div>
|
||||
<Heading title={data.extras.heading} />
|
||||
<div className="flex flex-col mb-6">
|
||||
{data.extras.items.filter(x => x.enable).map(ExtraItem)}
|
||||
{data.extras.items.filter((x) => x.enable).map(ExtraItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const WorkItem = x => (
|
||||
const WorkItem = (x) => (
|
||||
<div key={x.title} className="mb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -158,12 +158,12 @@ const Pikachu = () => {
|
||||
<div>
|
||||
<Heading title={data.work.heading} />
|
||||
<div className="flex flex-col mb-4">
|
||||
{data.work.items.filter(x => x.enable).map(WorkItem)}
|
||||
{data.work.items.filter((x) => x.enable).map(WorkItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const EducationItem = x => (
|
||||
const EducationItem = (x) => (
|
||||
<div key={x.name} className="mb-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
@ -189,12 +189,12 @@ const Pikachu = () => {
|
||||
<div>
|
||||
<Heading title={data.education.heading} />
|
||||
<div className="flex flex-col mb-4">
|
||||
{data.education.items.filter(x => x.enable).map(EducationItem)}
|
||||
{data.education.items.filter((x) => x.enable).map(EducationItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const AwardItem = x => (
|
||||
const AwardItem = (x) => (
|
||||
<div key={x.title} className="mb-3">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -208,12 +208,12 @@ const Pikachu = () => {
|
||||
<div>
|
||||
<Heading title={data.awards.heading} />
|
||||
<div className="flex flex-col mb-2">
|
||||
{data.awards.items.filter(x => x.enable).map(AwardItem)}
|
||||
{data.awards.items.filter((x) => x.enable).map(AwardItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const CertificationItem = x => (
|
||||
const CertificationItem = (x) => (
|
||||
<div key={x.title} className="mb-3">
|
||||
<h6 className="font-semibold">{x.title}</h6>
|
||||
<p className="text-xs">{x.subtitle}</p>
|
||||
@ -227,7 +227,7 @@ const Pikachu = () => {
|
||||
<div>
|
||||
<Heading title={data.certifications.heading} />
|
||||
<div className="flex flex-col mb-2">
|
||||
{data.certifications.items.filter(x => x.enable).map(CertificationItem)}
|
||||
{data.certifications.items.filter((x) => x.enable).map(CertificationItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -10,7 +10,7 @@ const move = (array, element, delta) => {
|
||||
array.splice(indexes[0], 2, array[indexes[1]], array[indexes[0]]);
|
||||
};
|
||||
|
||||
const hexToRgb = hex => {
|
||||
const hexToRgb = (hex) => {
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
@ -23,7 +23,7 @@ const hexToRgb = hex => {
|
||||
: null;
|
||||
};
|
||||
|
||||
const copyToClipboard = text => {
|
||||
const copyToClipboard = (text) => {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.top = 0;
|
||||
@ -44,7 +44,7 @@ const copyToClipboard = text => {
|
||||
return successful;
|
||||
};
|
||||
|
||||
const saveData = dispatch => dispatch({ type: 'save_data' });
|
||||
const saveData = (dispatch) => dispatch({ type: 'save_data' });
|
||||
|
||||
const addItem = (dispatch, key, value) => {
|
||||
dispatch({
|
||||
@ -104,42 +104,88 @@ const importJson = (event, dispatch) => {
|
||||
fr.readAsText(event.target.files[0]);
|
||||
};
|
||||
|
||||
const saveAsPdf = (pageRef, panZoomRef) => {
|
||||
panZoomRef.current.autoCenter(1);
|
||||
panZoomRef.current.reset();
|
||||
const saveAsPdf = (pageRef, panZoomRef, quality, type) =>
|
||||
new Promise((resolve) => {
|
||||
panZoomRef.current.autoCenter(1);
|
||||
panZoomRef.current.reset();
|
||||
|
||||
setTimeout(() => {
|
||||
html2canvas(pageRef.current, {
|
||||
scale: 6,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
}).then(canvas => {
|
||||
const image = canvas.toDataURL('image/jpeg', 1.0);
|
||||
const doc = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'px',
|
||||
format: [canvas.width, canvas.height],
|
||||
setTimeout(() => {
|
||||
html2canvas(pageRef.current, {
|
||||
scale: 5,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
}).then((canvas) => {
|
||||
const image = canvas.toDataURL('image/jpeg', quality / 100);
|
||||
const doc = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'px',
|
||||
format: type === 'unconstrained' ? [canvas.width, canvas.height] : '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;
|
||||
|
||||
let marginX = 0;
|
||||
let marginY = 0;
|
||||
|
||||
if (type !== 'unconstrained') {
|
||||
marginX = (pageWidth - canvasWidth) / 2;
|
||||
marginY = (pageHeight - canvasHeight) / 2;
|
||||
}
|
||||
|
||||
doc.addImage(image, 'JPEG', marginX, marginY, canvasWidth, canvasHeight, null, 'SLOW');
|
||||
doc.save(`RxResume_${Date.now()}.pdf`);
|
||||
resolve();
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
|
||||
const pageWidth = doc.internal.pageSize.getWidth();
|
||||
const pageHeight = doc.internal.pageSize.getHeight();
|
||||
const saveAsMultiPagePdf = (pageRef, panZoomRef, quality) =>
|
||||
new Promise((resolve) => {
|
||||
panZoomRef.current.autoCenter(1);
|
||||
panZoomRef.current.reset();
|
||||
|
||||
const widthRatio = pageWidth / canvas.width;
|
||||
const heightRatio = pageHeight / canvas.height;
|
||||
const ratio = widthRatio > heightRatio ? heightRatio : widthRatio;
|
||||
setTimeout(() => {
|
||||
html2canvas(pageRef.current, {
|
||||
scale: 5,
|
||||
useCORS: true,
|
||||
allowTaint: true,
|
||||
}).then((canvas) => {
|
||||
const image = canvas.toDataURL('image/jpeg', quality / 100);
|
||||
const doc = new jsPDF({
|
||||
orientation: 'portrait',
|
||||
unit: 'px',
|
||||
format: 'a4',
|
||||
});
|
||||
|
||||
const canvasWidth = canvas.width * ratio;
|
||||
const canvasHeight = canvas.height * ratio;
|
||||
// const marginX = (pageWidth - canvasWidth) / 2;
|
||||
// const marginY = (pageHeight - canvasHeight) / 2;
|
||||
const pageHeight = doc.internal.pageSize.getHeight();
|
||||
const canvasWidth = doc.internal.pageSize.getWidth();
|
||||
const canvasHeight = (canvas.height * canvasWidth) / canvas.width;
|
||||
let marginTop = 0;
|
||||
let heightLeft = canvasHeight;
|
||||
|
||||
panZoomRef.current.autoCenter(0.7);
|
||||
doc.addImage(image, 'JPEG', 0, marginTop, canvasWidth, canvasHeight);
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
doc.addImage(image, 'JPEG', 0, 0, canvasWidth, canvasHeight, null, 'SLOW');
|
||||
doc.save(`RxResume_${Date.now()}.pdf`);
|
||||
});
|
||||
}, 200);
|
||||
};
|
||||
while (heightLeft >= 0) {
|
||||
marginTop = heightLeft - canvasHeight;
|
||||
doc.addPage();
|
||||
doc.addImage(image, 'JPEG', 0, marginTop, canvasWidth, canvasHeight);
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
doc.save(`RxResume_${Date.now()}.pdf`);
|
||||
resolve();
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
|
||||
export {
|
||||
move,
|
||||
@ -152,4 +198,5 @@ export {
|
||||
moveItemDown,
|
||||
importJson,
|
||||
saveAsPdf,
|
||||
saveAsMultiPagePdf,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user