Merge pull request #75 from AmruthPillai/develop

Feature: Dialog to Download Your Resume
This commit is contained in:
Amruth Pillai
2020-04-05 01:28:20 +05:30
committed by GitHub
22 changed files with 444 additions and 119 deletions
+4
View File
@@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file.
## April 2020
### April 5, 2020
- Added Print Dialog to set Quality & Print Type before Exporting PDF
### April 3, 2020
- Added Language: German
+5
View File
@@ -44,6 +44,11 @@ Used by more than 800,000 web developers and businesses, the Netlify platform pr
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/AmruthPillai/Reactive-Resume)
## Deploy to Synology NAS
**NAS Hosted** has written a great tutorial on how to deploy the app on Synology NAS,
read more about it here: [Host your own Resume Builder on Synology using Docker](https://nashosted.com/host-your-own-resume-builder-on-synology-using-docker/)
## Deploy to Shared Hosting/VPS
Here, you're kinda on your own as there are no one-click buttons to help you through, but it's a simple process. Once you've built the app, you can copy the contents of the `build` folder to your `public_html` folder and you will have the app running on your designated domain or IP address.
+5 -2
View File
@@ -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>
);
+5 -4
View File
@@ -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">
+1 -1
View File
@@ -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 -1
View File
@@ -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}
+18
View File
@@ -16,5 +16,23 @@
"label": "Hinzufügen"
}
},
"printDialog": {
"heading": "Lade Dein Resume herunter",
"quality": {
"label": "Qualität"
},
"printType": {
"label": "Typ",
"types": {
"unconstrained": "Uneingeschränkt",
"fitInA4": "A4 einbauen",
"multiPageA4": "Mehrseitig A4"
}
},
"buttons": {
"cancel": "Abbrechen",
"saveAsPdf": "Als PDF Speichern"
}
},
"markdownHelpText": "Du kannst <1>GitHub Flavored Markdown</1> verwenden, um diesen Abschnitt des Textes zu gestalten."
}
+18
View File
@@ -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."
}
+18
View File
@@ -16,5 +16,23 @@
"label": "Ajouter"
}
},
"printDialog": {
"heading": "Téléchargez Votre CV",
"quality": {
"label": "Qualité"
},
"printType": {
"label": "Type",
"types": {
"unconstrained": "Non Contraint",
"fitInA4": "Convient au format A4",
"multiPageA4": "Multi-Page A4"
}
},
"buttons": {
"cancel": "Abbrechen",
"saveAsPdf": "Enregistrer en PDF"
}
},
"markdownHelpText": "Vous pouvez utiliser <1>GitHub Flavored Markdown</1> pour styliser cette section du texte."
}
+18
View File
@@ -16,5 +16,23 @@
"label": "जोड़ना"
}
},
"printDialog": {
"heading": "अपना रिज्यूमे डाउनलोड करें",
"quality": {
"label": "गुणवत्ता मूल्य"
},
"printType": {
"label": "प्रकार",
"types": {
"unconstrained": "स्वेच्छापूर्ण",
"fitInA4": "A4 में फिट",
"multiPageA4": "बहु पृष्ठ A4"
}
},
"buttons": {
"cancel": "रद्द करें",
"saveAsPdf": "डाउनलोड पीडीऍफ़"
}
},
"markdownHelpText": "आप पाठ के इस खंड को स्टाइल करने के लिए <1>GitHub Flavoured Markdown</1> का उपयोग कर सकते हैं।"
}
+18
View File
@@ -16,5 +16,23 @@
"label": "ಸೇರಿಸಿ"
}
},
"printDialog": {
"heading": "ನಿಮ್ಮ ರೇಸುಮೇ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ",
"quality": {
"label": "ಗುಣಮಟ್ಟದ ಮೌಲ್ಯ"
},
"printType": {
"label": "ವಿಧ",
"types": {
"unconstrained": "ನಿರ್ಬಂಧಿಸದ",
"fitInA4": "ಎ4 ನಲ್ಲಿ ಹೊಂದಿಕೊಳ್ಳುತ್ತದೆ",
"multiPageA4": "ಬಹು ಪುಟ ಎ4"
}
},
"buttons": {
"cancel": "ತ್ಯಜಿಸಿ",
"saveAsPdf": "ಪಿಡಿಎಫ್ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ"
}
},
"markdownHelpText": "ಪಠ್ಯದ ಈ ವಿಭಾಗವನ್ನು ವಿನ್ಯಾಸಗೊಳಿಸಲು ನೀವು <1>ಗಿಟ್‌ಹಬ್ ಫ್ಲೇವರ್ಡ್ ಮಾರ್ಕ್‌ಡೌನ್</1> ಅನ್ನು ಬಳಸಬಹುದು."
}
+18
View File
@@ -16,5 +16,23 @@
"label": "添加"
}
},
"printDialog": {
"heading": "下载简历",
"quality": {
"label": "品质"
},
"printType": {
"label": "类型",
"types": {
"unconstrained": "不受限制",
"fitInA4": "适合A4",
"multiPageA4": "多页A4"
}
},
"buttons": {
"cancel": "取消",
"saveAsPdf": "保存为 PDF"
}
},
"markdownHelpText": "你可以使用 <1>GitHub 倾向的 Markdown</1> 来美化这部分文字."
}
+18
View File
@@ -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."
}
+29
View File
@@ -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 {
+3 -3
View File
@@ -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
View 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;
+16 -16
View File
@@ -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,
+15 -15
View File
@@ -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>
);
+17 -15
View File
@@ -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>
);
+17 -15
View File
@@ -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>
);
+15 -15
View File
@@ -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>
);
+79 -32
View File
@@ -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,
};