mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-12 15:52:56 +10:00
Feature: Toggle Page Size between ISO A4 and US Letter
This commit is contained in:
@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.break::after {
|
&.break::after {
|
||||||
content: 'A4 Page Break';
|
content: 'Page Break';
|
||||||
top: calc(297mm - 19px);
|
top: calc(297mm - 19px);
|
||||||
|
|
||||||
@apply absolute w-full border-b border-dashed border-neutral-800/75;
|
@apply absolute w-full border-b border-dashed border-neutral-800/75;
|
||||||
@ -28,6 +28,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.format-letter {
|
||||||
|
width: 216mm;
|
||||||
|
min-height: 279mm;
|
||||||
|
|
||||||
|
&.break::after {
|
||||||
|
top: calc(279mm - 19px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
ul {
|
ul {
|
||||||
padding-left: 1.5em;
|
padding-left: 1.5em;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { CustomCSS, Theme, Typography } from '@reactive-resume/schema';
|
import { CustomCSS, PageConfig, ThemeConfig, Typography } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -23,9 +23,10 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
|||||||
const resume = useAppSelector((state) => state.resume.present);
|
const resume = useAppSelector((state) => state.resume.present);
|
||||||
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
const breakLine: boolean = useAppSelector((state) => state.build.page.breakLine);
|
||||||
|
|
||||||
const theme: Theme = get(resume, 'metadata.theme');
|
const theme: ThemeConfig = get(resume, 'metadata.theme');
|
||||||
const customCSS: CustomCSS = get(resume, 'metadata.css');
|
const customCSS: CustomCSS = get(resume, 'metadata.css');
|
||||||
const template: string = get(resume, 'metadata.template');
|
const template: string = get(resume, 'metadata.template');
|
||||||
|
const pageConfig: PageConfig = get(resume, 'metadata.page');
|
||||||
const typography: Typography = get(resume, 'metadata.typography');
|
const typography: Typography = get(resume, 'metadata.typography');
|
||||||
|
|
||||||
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
|
const themeCSS = useMemo(() => !isEmpty(theme) && generateThemeStyles(theme), [theme]);
|
||||||
@ -33,7 +34,7 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
|||||||
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
|
const TemplatePage: React.FC<PageProps> | null = useMemo(() => templateMap[template].component, [template]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-page={page + 1} className={styles.container}>
|
<div data-page={page + 1} data-format={pageConfig.format || 'A4'} className={styles.container}>
|
||||||
<div
|
<div
|
||||||
className={clsx({
|
className={clsx({
|
||||||
reset: true,
|
reset: true,
|
||||||
@ -42,6 +43,7 @@ const Page: React.FC<Props> = ({ page, showPageNumbers = false }) => {
|
|||||||
[css(themeCSS)]: true,
|
[css(themeCSS)]: true,
|
||||||
[css(typographyCSS)]: true,
|
[css(typographyCSS)]: true,
|
||||||
[css(customCSS.value)]: customCSS.visible,
|
[css(customCSS.value)]: customCSS.visible,
|
||||||
|
[styles['format-letter']]: pageConfig?.format === 'Letter',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{TemplatePage && <TemplatePage page={page} />}
|
{TemplatePage && <TemplatePage page={page} />}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
TextField,
|
TextField,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { DateConfig, Resume } from '@reactive-resume/schema';
|
import { DateConfig, PageConfig, Resume } from '@reactive-resume/schema';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@ -47,10 +47,11 @@ const Settings = () => {
|
|||||||
const id: number = useMemo(() => get(resume, 'id'), [resume]);
|
const id: number = useMemo(() => get(resume, 'id'), [resume]);
|
||||||
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
|
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
|
||||||
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
|
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
|
||||||
|
const pageConfig: PageConfig = useMemo(() => get(resume, 'metadata.page'), [resume]);
|
||||||
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
|
||||||
|
|
||||||
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
|
||||||
const exampleString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
const exampleDateString = useMemo(() => `Eg. ${dayjs().utc().format(dateConfig.format)}`, [dateConfig.format]);
|
||||||
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
const themeString = useMemo(() => (isDarkMode ? 'Matte Black Everything' : 'As bright as your future'), [isDarkMode]);
|
||||||
|
|
||||||
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
const { mutateAsync: loadSampleDataMutation } = useMutation<Resume, ServerError, LoadSampleDataParams>(
|
||||||
@ -60,6 +61,9 @@ const Settings = () => {
|
|||||||
|
|
||||||
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
|
const handleSetTheme = (value: boolean) => dispatch(setTheme({ theme: value ? 'dark' : 'light' }));
|
||||||
|
|
||||||
|
const handleChangePageFormat = (value: PageConfig['format'] | null) =>
|
||||||
|
dispatch(setResumeState({ path: 'metadata.page.format', value }));
|
||||||
|
|
||||||
const handleChangeDateFormat = (value: string | null) =>
|
const handleChangeDateFormat = (value: string | null) =>
|
||||||
dispatch(setResumeState({ path: 'metadata.date.format', value }));
|
dispatch(setResumeState({ path: 'metadata.date.format', value }));
|
||||||
|
|
||||||
@ -118,13 +122,13 @@ const Settings = () => {
|
|||||||
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
|
primary={t<string>('builder.rightSidebar.sections.settings.global.date.primary')}
|
||||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
|
secondary={t<string>('builder.rightSidebar.sections.settings.global.date.secondary')}
|
||||||
/>
|
/>
|
||||||
<Autocomplete<string, false, boolean, false>
|
<Autocomplete<string, false, true, false>
|
||||||
disableClearable
|
disableClearable
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
options={dateFormatOptions}
|
options={dateFormatOptions}
|
||||||
value={dateConfig.format}
|
value={dateConfig.format}
|
||||||
onChange={(_, value) => handleChangeDateFormat(value)}
|
onChange={(_, value) => handleChangeDateFormat(value)}
|
||||||
renderInput={(params) => <TextField {...params} helperText={exampleString} />}
|
renderInput={(params) => <TextField {...params} helperText={exampleDateString} />}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
@ -134,7 +138,7 @@ const Settings = () => {
|
|||||||
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
|
primary={t<string>('builder.rightSidebar.sections.settings.global.language.primary')}
|
||||||
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
|
secondary={t<string>('builder.rightSidebar.sections.settings.global.language.secondary')}
|
||||||
/>
|
/>
|
||||||
<Autocomplete<Language, false, boolean, false>
|
<Autocomplete<Language, false, true, false>
|
||||||
disableClearable
|
disableClearable
|
||||||
className="my-2 w-full"
|
className="my-2 w-full"
|
||||||
options={languages}
|
options={languages}
|
||||||
@ -159,6 +163,23 @@ const Settings = () => {
|
|||||||
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
{t<string>('builder.rightSidebar.sections.settings.page.heading')}
|
||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
|
|
||||||
|
<ListItem className="flex-col">
|
||||||
|
<ListItemText
|
||||||
|
className="w-full"
|
||||||
|
primary={t<string>('builder.rightSidebar.sections.settings.page.format.primary')}
|
||||||
|
secondary={t<string>('builder.rightSidebar.sections.settings.page.format.secondary')}
|
||||||
|
/>
|
||||||
|
<Autocomplete<PageConfig['format'], false, true, false>
|
||||||
|
disableClearable
|
||||||
|
defaultValue="A4"
|
||||||
|
className="my-2 w-full"
|
||||||
|
options={['A4', 'Letter']}
|
||||||
|
value={pageConfig?.format || 'A4'}
|
||||||
|
renderInput={(params) => <TextField {...params} />}
|
||||||
|
onChange={(_, value) => handleChangePageFormat(value)}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
primary={t<string>('builder.rightSidebar.sections.settings.page.orientation.primary')}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Theme as ThemeType } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ const Theme = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { background, text, primary } = useAppSelector<ThemeType>((state) =>
|
const { background, text, primary } = useAppSelector<ThemeConfig>((state) =>
|
||||||
get(state.resume.present, 'metadata.theme')
|
get(state.resume.present, 'metadata.theme')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -13,22 +13,22 @@
|
|||||||
"@emotion/css": "^11.10.0",
|
"@emotion/css": "^11.10.0",
|
||||||
"@emotion/react": "^11.10.4",
|
"@emotion/react": "^11.10.4",
|
||||||
"@emotion/styled": "^11.10.4",
|
"@emotion/styled": "^11.10.4",
|
||||||
"@hello-pangea/dnd": "^16.0.0",
|
"@hello-pangea/dnd": "^16.0.1",
|
||||||
"@hookform/resolvers": "2.9.8",
|
"@hookform/resolvers": "2.9.8",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@monaco-editor/react": "^4.4.6",
|
||||||
"@mui/icons-material": "^5.10.6",
|
"@mui/icons-material": "^5.10.9",
|
||||||
"@mui/lab": "^5.0.0-alpha.102",
|
"@mui/lab": "^5.0.0-alpha.103",
|
||||||
"@mui/material": "^5.10.8",
|
"@mui/material": "^5.10.9",
|
||||||
"@mui/system": "^5.10.8",
|
"@mui/system": "^5.10.9",
|
||||||
"@mui/x-date-pickers": "5.0.4",
|
"@mui/x-date-pickers": "5.0.4",
|
||||||
"@next/env": "^12.3.1",
|
"@next/env": "^12.3.1",
|
||||||
"@react-oauth/google": "^0.2.8",
|
"@react-oauth/google": "^0.2.8",
|
||||||
"@reduxjs/toolkit": "^1.8.5",
|
"@reduxjs/toolkit": "^1.8.6",
|
||||||
"axios": "^1.1.0",
|
"axios": "^1.1.2",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
"downloadjs": "^1.4.7",
|
"downloadjs": "^1.4.7",
|
||||||
"joi": "^17.6.2",
|
"joi": "^17.6.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"md5-hex": "^4.0.0",
|
"md5-hex": "^4.0.0",
|
||||||
"monaco-editor": "^0.34.0",
|
"monaco-editor": "^0.34.0",
|
||||||
@ -64,7 +64,7 @@
|
|||||||
"@tailwindcss/typography": "^0.5.7",
|
"@tailwindcss/typography": "^0.5.7",
|
||||||
"@types/downloadjs": "^1.4.3",
|
"@types/downloadjs": "^1.4.3",
|
||||||
"@types/lodash": "^4.14.186",
|
"@types/lodash": "^4.14.186",
|
||||||
"@types/node": "^18.8.3",
|
"@types/node": "^18.8.5",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.0.21",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/react-redux": "^7.1.24",
|
"@types/react-redux": "^7.1.24",
|
||||||
@ -75,8 +75,8 @@
|
|||||||
"csstype": "^3.1.1",
|
"csstype": "^3.1.1",
|
||||||
"eslint-config-next": "^12.3.1",
|
"eslint-config-next": "^12.3.1",
|
||||||
"eslint-plugin-tailwindcss": "^3.6.2",
|
"eslint-plugin-tailwindcss": "^3.6.2",
|
||||||
"next-sitemap": "^3.1.23",
|
"next-sitemap": "^3.1.25",
|
||||||
"postcss": "^8.4.17",
|
"postcss": "^8.4.18",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.55.0",
|
||||||
"tailwindcss": "^3.1.8",
|
"tailwindcss": "^3.1.8",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "Vášnivý projekt <1>Amrutha Pillaie</1>",
|
"credit": "Vášnivý projekt <1>Amruth Pillai</1>",
|
||||||
"license": "Od komunity, pro komunitu."
|
"license": "Od komunity, pro komunitu."
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -290,6 +290,10 @@
|
|||||||
},
|
},
|
||||||
"heading": "Settings",
|
"heading": "Settings",
|
||||||
"page": {
|
"page": {
|
||||||
|
"format": {
|
||||||
|
"primary": "Paper Size",
|
||||||
|
"secondary": "Determines the dimensions of your resume pages"
|
||||||
|
},
|
||||||
"break-line": {
|
"break-line": {
|
||||||
"primary": "Break Line",
|
"primary": "Break Line",
|
||||||
"secondary": "Show a line on all pages to mark the height of an A4 page"
|
"secondary": "Show a line on all pages to mark the height of an A4 page"
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "<1>Amruth Pillai szenvedélyes projektje</1>",
|
"credit": "<1>Amruth Pillai</1> szenvedélyes projektje",
|
||||||
"license": "A közösség által, a közösségért."
|
"license": "A közösség által, a közösségért."
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "គម្រោងបង្កើតឡើងដោយលោក <1> Amruth Pillai</1>",
|
"credit": "គម្រោងបង្កើតឡើងដោយលោក <1>Amruth Pillai</1>",
|
||||||
"license": "ដោយសហគមន៍ ដើម្បីសហគមន៍។"
|
"license": "ដោយសហគមន៍ ដើម្បីសហគមន៍។"
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "<1>Amruth Pillai의 열정 프로젝트</1>",
|
"credit": "<1>Amruth Pillai의</1> 열정 프로젝트",
|
||||||
"license": "커뮤니티에 의한, 커뮤니티를 위한."
|
"license": "커뮤니티에 의한, 커뮤니티를 위한."
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "<1>अमृत पिल्लई यांचा एक उत्कट प्रकल्प</1>",
|
"credit": "<1>अमृत पिल्लई</1> यांचा एक उत्कट प्रकल्प",
|
||||||
"license": "समाजाने, समाजासाठी."
|
"license": "समाजाने, समाजासाठी."
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "<1> अमृत पिल्लै द्वारा एक जोश परियोजना</1>",
|
"credit": "<1>अमृत पिल्लै</1> द्वारा एक जोश परियोजना",
|
||||||
"license": "समुदाय द्वारा, समुदाय को लागी।"
|
"license": "समुदाय द्वारा, समुदाय को लागी।"
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "<1> ଅମୃତ ପିଲ୍ଲାଇ</1> ଦ୍ୱାରା ଏକ ଉତ୍ସାହ ପ୍ରୋଜେକ୍ଟ ।",
|
"credit": "<1>ଅମୃତ ପିଲ୍ଲାଇ</1> ଦ୍ୱାରା ଏକ ଉତ୍ସାହ ପ୍ରୋଜେକ୍ଟ ।",
|
||||||
"license": "ସମ୍ପ୍ରଦାୟ ଦ୍ୱାରା, ସମ୍ପ୍ରଦାୟ ପାଇଁ ।"
|
"license": "ସମ୍ପ୍ରଦାୟ ଦ୍ୱାରା, ସମ୍ପ୍ରଦାୟ ପାଇଁ ।"
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"credit": "Một dự án làm với đam mê của <1> Amruth Pillai</1>",
|
"credit": "Một dự án làm với đam mê của <1>Amruth Pillai</1>",
|
||||||
"license": "Vì cộng đồng, cho cộng đồng."
|
"license": "Vì cộng đồng, cho cộng đồng."
|
||||||
},
|
},
|
||||||
"markdown": {
|
"markdown": {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -17,7 +17,7 @@ const Castform: React.FC<PageProps> = ({ page }) => {
|
|||||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||||
|
|
||||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { darken } from '@mui/material';
|
import { darken } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
|
const darkerPrimary = useMemo(() => darken(theme.primary, 0.2), [theme.primary]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -19,7 +19,7 @@ export const MastheadSidebar: React.FC = () => {
|
|||||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics
|
||||||
);
|
);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { alpha } from '@mui/material';
|
import { alpha } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -18,7 +18,7 @@ const Gengar: React.FC<PageProps> = ({ page }) => {
|
|||||||
const isFirstPage = useMemo(() => page === 0, [page]);
|
const isFirstPage = useMemo(() => page === 0, [page]);
|
||||||
|
|
||||||
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
const layout: string[][] = useAppSelector((state) => state.resume.present.metadata.layout[page]);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
const backgroundColor: string = useMemo(() => alpha(theme.primary, 0.15), [theme.primary]);
|
const backgroundColor: string = useMemo(() => alpha(theme.primary, 0.15), [theme.primary]);
|
||||||
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
const color = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h3
|
<h3
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { alpha } from '@mui/material';
|
import { alpha } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -20,7 +20,7 @@ export const MastheadSidebar: React.FC = () => {
|
|||||||
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, headline, photo, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics
|
||||||
);
|
);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
const iconColor = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
const iconColor = useMemo(() => (contrast === 'dark' ? theme.text : theme.background), [theme, contrast]);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { alpha } from '@mui/material';
|
import { alpha } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isArray from 'lodash/isArray';
|
import isArray from 'lodash/isArray';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -13,7 +13,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BadgeDisplay: React.FC<Props> = ({ items }) => {
|
const BadgeDisplay: React.FC<Props> = ({ items }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
|
|
||||||
if (!isArray(items) || isEmpty(items)) return null;
|
if (!isArray(items) || isEmpty(items)) return null;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h3
|
<h3
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isArray from 'lodash/isArray';
|
import isArray from 'lodash/isArray';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
@ -12,7 +12,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BadgeDisplay: React.FC<Props> = ({ items }) => {
|
const BadgeDisplay: React.FC<Props> = ({ items }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
|
|
||||||
if (!isArray(items) || isEmpty(items)) return null;
|
if (!isArray(items) || isEmpty(items)) return null;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h2
|
<h2
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { alpha } from '@mui/material';
|
import { alpha } from '@mui/material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ const Masthead: React.FC = () => {
|
|||||||
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
const { name, photo, headline, summary, email, phone, birthdate, website, location, profiles } = useAppSelector(
|
||||||
(state) => state.resume.present.basics
|
(state) => state.resume.present.basics
|
||||||
);
|
);
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h4 className="mb-2 font-bold uppercase" style={{ color: theme.primary }}>
|
<h4 className="mb-2 font-bold uppercase" style={{ color: theme.primary }}>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
import { useAppSelector } from '@/store/hooks';
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
const Heading: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h3
|
<h3
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
import { Cake, Email, Phone, Public, Room } from '@mui/icons-material';
|
||||||
import { Theme } from '@reactive-resume/schema';
|
import { ThemeConfig } from '@reactive-resume/schema';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@ -62,7 +62,7 @@ export const MastheadSidebar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MastheadMain: React.FC = () => {
|
export const MastheadMain: React.FC = () => {
|
||||||
const theme: Theme = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
const theme: ThemeConfig = useAppSelector((state) => get(state.resume.present, 'metadata.theme', {}));
|
||||||
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
const contrast = useMemo(() => getContrastColor(theme.primary), [theme.primary]);
|
||||||
|
|
||||||
const { name, summary, headline } = useAppSelector((state) => state.resume.present.basics);
|
const { name, summary, headline } = useAppSelector((state) => state.resume.present.basics);
|
||||||
|
|||||||
3
client/utils/string.ts
Normal file
3
client/utils/string.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const capitalize = (str: string) => {
|
||||||
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Theme, Typography } from '@reactive-resume/schema';
|
import { ThemeConfig, Typography } from '@reactive-resume/schema';
|
||||||
import { RgbColor } from 'react-colorful';
|
import { RgbColor } from 'react-colorful';
|
||||||
|
|
||||||
import { hexColorPattern } from '@/config/colors';
|
import { hexColorPattern } from '@/config/colors';
|
||||||
@ -27,7 +27,7 @@ export const generateTypographyStyles = ({ family, size }: Typography): string =
|
|||||||
h6 { font-size: ${size.heading / 3.5}px; line-height: ${size.heading / 3.5}px; }
|
h6 { font-size: ${size.heading / 3.5}px; line-height: ${size.heading / 3.5}px; }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const generateThemeStyles = ({ text, background, primary }: Theme): string => `
|
export const generateThemeStyles = ({ text, background, primary }: ThemeConfig): string => `
|
||||||
color: ${text};
|
color: ${text};
|
||||||
background-color: ${background};
|
background-color: ${background};
|
||||||
--primary-color: ${primary};
|
--primary-color: ${primary};
|
||||||
|
|||||||
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "reactive-resume",
|
"name": "reactive-resume",
|
||||||
"version": "3.6.7",
|
"version": "3.6.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "env-cmd --silent turbo run dev",
|
"dev": "env-cmd --silent turbo run dev",
|
||||||
@ -17,12 +17,12 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"turbo": "^1.5.5"
|
"turbo": "^1.5.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^5.39.0",
|
"@typescript-eslint/eslint-plugin": "^5.40.0",
|
||||||
"@typescript-eslint/parser": "^5.39.0",
|
"@typescript-eslint/parser": "^5.40.0",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.25.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
|||||||
1474
pnpm-lock.yaml
generated
1474
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@
|
|||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.25.0",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,11 @@ export type CustomCSS = {
|
|||||||
visible: boolean;
|
visible: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Theme = {
|
export type PageConfig = {
|
||||||
|
format: 'A4' | 'Letter';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ThemeConfig = {
|
||||||
text: string;
|
text: string;
|
||||||
background: string;
|
background: string;
|
||||||
primary: string;
|
primary: string;
|
||||||
@ -27,6 +31,7 @@ export type Metadata = {
|
|||||||
date: DateConfig;
|
date: DateConfig;
|
||||||
layout: string[][][]; // page.column.section
|
layout: string[][][]; // page.column.section
|
||||||
template: string;
|
template: string;
|
||||||
theme: Theme;
|
theme: ThemeConfig;
|
||||||
|
page?: PageConfig;
|
||||||
typography: Typography;
|
typography: Typography;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"start": "node dist/main"
|
"start": "node dist/main"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.186.0",
|
"@aws-sdk/client-s3": "^3.188.0",
|
||||||
"@nestjs/axios": "^0.1.0",
|
"@nestjs/axios": "^0.1.0",
|
||||||
"@nestjs/common": "^9.1.4",
|
"@nestjs/common": "^9.1.4",
|
||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
@ -23,14 +23,14 @@
|
|||||||
"@nestjs/typeorm": "^9.0.1",
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
"@types/passport": "^1.0.11",
|
"@types/passport": "^1.0.11",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cache-manager": "^5.0.0",
|
"cache-manager": "^5.0.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"csvtojson": "^2.0.10",
|
"csvtojson": "^2.0.10",
|
||||||
"dayjs": "^1.11.5",
|
"dayjs": "^1.11.5",
|
||||||
"google-auth-library": "^8.5.2",
|
"google-auth-library": "^8.5.2",
|
||||||
"joi": "^17.6.2",
|
"joi": "^17.6.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multer": "^1.4.4",
|
"multer": "^1.4.4",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pg": "^8.8.0",
|
"pg": "^8.8.0",
|
||||||
"playwright-chromium": "^1.26.1",
|
"playwright-chromium": "^1.27.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.5.7",
|
"rxjs": "^7.5.7",
|
||||||
@ -57,7 +57,7 @@
|
|||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"@types/lodash": "^4.14.186",
|
"@types/lodash": "^4.14.186",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^18.8.3",
|
"@types/node": "^18.8.5",
|
||||||
"@types/nodemailer": "^6.4.6",
|
"@types/nodemailer": "^6.4.6",
|
||||||
"@types/passport-jwt": "^3.0.7",
|
"@types/passport-jwt": "^3.0.7",
|
||||||
"@types/passport-local": "^1.0.34",
|
"@types/passport-local": "^1.0.34",
|
||||||
|
|||||||
@ -932,6 +932,9 @@ export class IntegrationsService {
|
|||||||
body: get(jsonResume, 'metadata.fontSize'),
|
body: get(jsonResume, 'metadata.fontSize'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
page: {
|
||||||
|
format: 'A4',
|
||||||
|
},
|
||||||
theme: {
|
theme: {
|
||||||
background: get(jsonResume, 'metadata.colors.background'),
|
background: get(jsonResume, 'metadata.colors.background'),
|
||||||
primary: get(jsonResume, 'metadata.colors.primary'),
|
primary: get(jsonResume, 'metadata.colors.primary'),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||||
|
import { PageConfig } from '@reactive-resume/schema';
|
||||||
import { mkdir, unlink, writeFile } from 'fs/promises';
|
import { mkdir, unlink, writeFile } from 'fs/promises';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
@ -35,13 +36,18 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
|||||||
await page.goto(`${url}/${username}/${slug}/printer?secretKey=${secretKey}`);
|
await page.goto(`${url}/${username}/${slug}/printer?secretKey=${secretKey}`);
|
||||||
await page.waitForSelector('html.wf-active');
|
await page.waitForSelector('html.wf-active');
|
||||||
|
|
||||||
const resumePages = await page.$$eval('[data-page]', (pages) => {
|
const pageFormat: PageConfig['format'] = await page.$$eval(
|
||||||
return pages.map((page, index) => ({
|
'[data-page]',
|
||||||
|
(pages) => pages[0].getAttribute('data-format') as PageConfig['format']
|
||||||
|
);
|
||||||
|
|
||||||
|
const resumePages = await page.$$eval('[data-page]', (pages) =>
|
||||||
|
pages.map((page, index) => ({
|
||||||
pageNumber: index + 1,
|
pageNumber: index + 1,
|
||||||
innerHTML: page.innerHTML,
|
innerHTML: page.innerHTML,
|
||||||
height: page.clientHeight,
|
height: page.clientHeight,
|
||||||
}));
|
}))
|
||||||
});
|
);
|
||||||
|
|
||||||
const pdf = await PDFDocument.create();
|
const pdf = await PDFDocument.create();
|
||||||
const directory = join(__dirname, '..', 'assets/exports');
|
const directory = join(__dirname, '..', 'assets/exports');
|
||||||
@ -52,9 +58,9 @@ export class PrinterService implements OnModuleInit, OnModuleDestroy {
|
|||||||
await page.evaluate((page) => (document.body.innerHTML = page.innerHTML), resumePages[index]);
|
await page.evaluate((page) => (document.body.innerHTML = page.innerHTML), resumePages[index]);
|
||||||
|
|
||||||
const buffer = await page.pdf({
|
const buffer = await page.pdf({
|
||||||
width: '210mm',
|
|
||||||
printBackground: true,
|
printBackground: true,
|
||||||
height: resumePages[index].height,
|
height: resumePages[index].height,
|
||||||
|
width: pageFormat === 'A4' ? '210mm' : '216mm',
|
||||||
});
|
});
|
||||||
|
|
||||||
const pageDoc = await PDFDocument.load(buffer);
|
const pageDoc = await PDFDocument.load(buffer);
|
||||||
|
|||||||
@ -138,6 +138,9 @@ const defaultState: Partial<Resume> = {
|
|||||||
date: {
|
date: {
|
||||||
format: 'MMMM DD, YYYY',
|
format: 'MMMM DD, YYYY',
|
||||||
},
|
},
|
||||||
|
page: {
|
||||||
|
format: 'A4',
|
||||||
|
},
|
||||||
layout: [
|
layout: [
|
||||||
[
|
[
|
||||||
['work', 'education', 'projects', 'volunteer', 'references'],
|
['work', 'education', 'projects', 'volunteer', 'references'],
|
||||||
|
|||||||
Reference in New Issue
Block a user