Created
March 2, 2020 18:20
-
-
Save chadwilken/caa6d945b5d5505fe1788f53af2be244 to your computer and use it in GitHub Desktop.
React PDF w/ UGC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useMemo } from 'react'; | |
import { | |
Document as PDFDocument, | |
StyleSheet, | |
Page, | |
Font, | |
} from '@react-pdf/renderer'; | |
import dig from 'lodash.get'; | |
import PageFooter from './PageFooter'; | |
import CoverPage from './CoverPage'; | |
import Entry from './Entry'; | |
Font.register({ | |
family: 'Public Sans', | |
fonts: [ | |
{ src: 'https://cdn.companycam.com/fonts/PublicSans-Regular.ttf' }, | |
{ | |
src: 'https://cdn.companycam.com/fonts/PublicSans-SemiBold.ttf', | |
fontWeight: 700, | |
}, | |
], | |
}); | |
Font.registerEmojiSource({ | |
format: 'png', | |
url: 'https://twemoji.maxcdn.com/2/72x72/', | |
}); | |
const styles = StyleSheet.create({ | |
// ... omitted | |
}); | |
const Document = ({ report, imageSize }) => { | |
const { | |
entries, | |
settings, | |
photoCount, | |
company, | |
title, | |
subtitle, | |
createdAt, | |
featuredEntry | |
} = report; | |
const { name, logoLargeUrl } = company; | |
const featuredPhoto = featuredEntry.assetPreviewLarge; | |
return ( | |
<PDFDocument> | |
<Page style={pageStyles} size="LETTER"> | |
<PageFooter title={title} /> | |
<CoverPage | |
companyName={name} | |
logo={logoLargeUrl} | |
title={title} | |
subtitle={subtitle} | |
createdAt={createdAt} | |
featuredPhoto={featuredPhoto} | |
photoCount={photoCount} | |
/> | |
</Page> | |
<Page style={pageStyles} size="LETTER"> | |
<PageFooter title={title} /> | |
{entries.map((entry) => { | |
return ( | |
<Entry | |
key={entry.id} | |
entry={entry} | |
settings={settings} | |
/> | |
); | |
})} | |
</Page> | |
</PDFDocument> | |
); | |
}; | |
export default Document; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { StyleSheet, View, Image, Text, Link } from '@react-pdf/renderer'; | |
import RichText from '../RichText'; | |
const styles = StyleSheet.create({ | |
// ...omitted | |
}); | |
const Entry = ({ entry }) => { | |
const { item, pageBreak } = entry; | |
const { assetPreviewLarge } = item; | |
return ( | |
<View style={containerStyles} wrap={false} break={pageBreak}> | |
<Link | |
src={assetPreviewLarge} | |
style={imageContainerStyles} | |
target="_blank" | |
> | |
<Image | |
style={imageStyles} | |
source={{ | |
uri: assetPreviewLarge, | |
headers: { Pragma: 'no-cache', 'Cache-Control': 'no-cache' }, | |
}} | |
/> | |
</Link> | |
<View style={contentStyles}> | |
<RichText note={entry.notes} /> | |
</View> | |
</View> | |
); | |
}; | |
export default Entry; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect } from 'react'; | |
import { pdf } from '@react-pdf/renderer'; | |
import { pdfjs, Document as PDFDocument, Page as PDFPage } from 'react-pdf'; | |
import Spinner from 'components/shared/LoadingSpinner'; | |
import Document from './Document'; | |
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; | |
const PDF = ({ report }) => { | |
const [loading, setLoading] = useState(true); | |
const [documentURL, setDocumentURL] = useState(); | |
const [currentPage, setCurrentPage] = useState(1); | |
const [numPages, setNumPages] = useState(); | |
useEffect( | |
() => { | |
const generateBlob = async () => { | |
setLoading(true); | |
const blob = await pdf( | |
<Document report={report} />, | |
).toBlob(); | |
setLoading(false); | |
setDocumentURL(window.URL.createObjectURL(blob)); | |
}; | |
if (report) { | |
generateBlob(); | |
} else { | |
setLoading(false); | |
setDocumentURL(null); | |
} | |
}, | |
[report], | |
); | |
const prevPage = () => { | |
if (!loading) { | |
setCurrentPage(Math.max(1, currentPage - 1)); | |
} | |
}; | |
const nextPage = () => { | |
if (!loading) { | |
setCurrentPage(Math.min(numPages, currentPage + 1)); | |
} | |
}; | |
if (loading) { | |
return <Spinner message="Fetching Images" />; | |
} | |
const filename = `${report.title || 'document'}.pdf`; | |
return ( | |
<React.Fragment> | |
{loading && <div>Rendering PDF…</div>} | |
<div className="pdf-modal-options"> | |
<div className="pdf-modal-pagination"> | |
<button type="button" onClick={prevPage}> | |
<i className="mdi mdi-chevron-left" /> | |
</button> | |
<span> | |
Page {currentPage} / {numPages} | |
</span> | |
<button type="button" onClick={nextPage}> | |
<i className="mdi mdi-chevron-right" /> | |
</button> | |
</div> | |
<a | |
href={documentURL} | |
className="ccb-blue-small" | |
style={{ margin: 'auto 0 0' }} | |
download={filename} | |
> | |
Download PDF | |
</a> | |
</div> | |
<PDFDocument | |
file={documentURL} | |
onLoadSuccess={(result) => setNumPages(result.numPages)} | |
loading={<Spinner />} | |
> | |
<PDFPage | |
renderMode="svg" | |
pageNumber={currentPage} | |
/> | |
</PDFDocument> | |
</React.Fragment> | |
); | |
}; | |
export default PDF; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { | |
EditorState, | |
ContentState, | |
convertToRaw, | |
convertFromHTML, | |
} from 'draft-js'; | |
import { StyleSheet, View, Text, Link } from '@react-pdf/renderer'; | |
import redraft from 'redraft'; | |
const styles = StyleSheet.create({ | |
headingOne: { | |
marginBottom: 4, | |
color: '#3a4b56', | |
fontWeight: 700, | |
fontFamily: 'Public Sans', | |
lineHeight: 1.35, | |
fontSize: 12, | |
}, | |
text: { | |
marginBottom: 8, | |
color: '#6b7880', | |
fontFamily: 'Public Sans', | |
fontSize: 10, | |
lineHeight: 1.45, | |
}, | |
list: { | |
marginBottom: 8, | |
marginLeft: 6, | |
}, | |
listItem: { | |
marginBottom: 4, | |
}, | |
listItemText: { | |
color: '#6b7880', | |
fontFamily: 'Public Sans', | |
fontSize: 10, | |
lineHeight: 1.45, | |
}, | |
}); | |
const HeadingOne = ({ children }) => { | |
return ( | |
<View> | |
<Text style={styles.headingOne}>{children}</Text> | |
</View> | |
); | |
}; | |
const UnorderedList = ({ children, depth }) => { | |
return <View style={styles.list}>{children}</View>; | |
}; | |
const UnorderedListItem = ({ children }) => { | |
return ( | |
<View style={styles.listItem}> | |
<Text style={styles.listItemText}> | |
• <Text>{children}</Text> | |
</Text> | |
</View> | |
); | |
}; | |
const OrderedList = ({ children, depth }) => { | |
return <View style={styles.list}>{children}</View>; | |
}; | |
const OrderedListItem = ({ children, index }) => { | |
return ( | |
<View style={styles.listItem}> | |
<Text style={styles.listItemText}> | |
{index + 1}. <Text>{children}</Text> | |
</Text> | |
</View> | |
); | |
}; | |
const renderers = { | |
inline: { | |
// The key passed here is just an index based on rendering order inside a block | |
BOLD: (children, { key }) => { | |
return ( | |
<Text key={`bold-${key}`} style={{ fontWeight: 700 }}> | |
{children} | |
</Text> | |
); | |
}, | |
ITALIC: (children, { key }) => { | |
return ( | |
<Text key={`italic-${key}`} style={{ fontStyle: 'italic' }}> | |
{children} | |
</Text> | |
); | |
}, | |
UNDERLINE: (children, { key }) => { | |
return ( | |
<Text key={`underline-${key}`} style={{ textDecoration: 'underline' }}> | |
{children} | |
</Text> | |
); | |
}, | |
}, | |
/** | |
* Blocks receive children and depth | |
* Note that children are an array of blocks with same styling, | |
*/ | |
blocks: { | |
unstyled: (children, { keys }) => { | |
return children.map((child, index) => { | |
return ( | |
<View key={keys[index]}> | |
<Text style={styles.text}>{child}</Text> | |
</View> | |
); | |
}); | |
}, | |
'header-one': (children, { keys }) => { | |
return children.map((child, index) => { | |
return <HeadingOne key={keys[index]}>{child}</HeadingOne>; | |
}); | |
}, | |
'unordered-list-item': (children, { depth, keys }) => { | |
return ( | |
<UnorderedList key={keys[keys.length - 1]} depth={depth}> | |
{children.map((child, index) => ( | |
<UnorderedListItem key={keys[index]}>{child}</UnorderedListItem> | |
))} | |
</UnorderedList> | |
); | |
}, | |
'ordered-list-item': (children, { depth, keys }) => { | |
return ( | |
<OrderedList key={keys.join('|')} depth={depth}> | |
{children.map((child, index) => ( | |
<OrderedListItem key={keys[index]} index={index}> | |
{child} | |
</OrderedListItem> | |
))} | |
</OrderedList> | |
); | |
}, | |
}, | |
/** | |
* Entities receive children and the entity data | |
*/ | |
entities: { | |
// key is the entity key value from raw | |
LINK: (children, data, { key }) => ( | |
<Link key={key} src={data.url}> | |
{children} | |
</Link> | |
), | |
}, | |
}; | |
const RichText = ({ note }) => { | |
const blocksFromHTML = convertFromHTML(note); | |
const initialState = ContentState.createFromBlockArray( | |
blocksFromHTML.contentBlocks, | |
blocksFromHTML.entityMap, | |
); | |
const editorState = EditorState.createWithContent(initialState); | |
const rawContent = convertToRaw(editorState.getCurrentContent()); | |
return redraft(rawContent, renderers, { blockFallback: 'unstyled' }); | |
}; | |
export default RichText; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
hi chadwilken,
sorry im not quite follow with the Pagefooter & Pagecover,
"The Page, CoverPage and PageFooter are all self explanatory so I won’t show the code."
instead of showing the original code, can you share some examples?
are both of them filled with code below or else?:
<Document> <Page> <Text> some text or anything </Text> </Page> </Document>
Thank you