@jsreport/jsreport-pdf-utils
Version:
jsreport extension providing pdf operations like merge or concatenation
348 lines (296 loc) • 8.31 kB
JavaScript
import { Component, useState, useRef, useEffect, useCallback } from 'react'
import Studio from 'jsreport-studio'
const Content = (props) => {
const { close, options } = props
const [error, setError] = useState(null)
const [processing, setProcessing] = useState(false)
const [templateName, setTemplateName] = useState('toc')
const nameInputRef = useRef(null)
const { entity: mainTemplate } = options
useEffect(() => {
nameInputRef.current && nameInputRef.current.focus()
}, [])
const handleSave = useCallback(async () => {
if (processing) {
return
}
setError(null)
setProcessing(true)
try {
await Studio.api.post('/studio/validate-entity-name', {
data: {
name: templateName,
entitySet: 'templates',
folderShortid: mainTemplate.folder != null ? mainTemplate.folder.shortid : null
}
}, true)
} catch (e) {
setProcessing(false)
setError(`New table of contents template validation error: ${e.message}`)
return
}
let newTOCTemplate
try {
const response = await Studio.api.post('/odata/templates', {
data: {
name: templateName,
content: getDefaultTOCContent(),
engine: 'handlebars',
recipe: mainTemplate.recipe,
helpers: getDefaultTOCHelpers(),
chrome: mainTemplate.recipe === 'chrome-pdf' ? { marginTop: (mainTemplate.chrome || {}).marginTop || undefined, marginLeft: (mainTemplate.chrome || {}).marginLeft, marginRight: (mainTemplate.chrome || {}).marginRight, marginBottom: (mainTemplate.chrome || {}).marginBottom || undefined } : mainTemplate.chrome,
folder: mainTemplate.folder != null ? { shortid: mainTemplate.folder.shortid } : null
}
}, true)
response.__entitySet = 'templates'
newTOCTemplate = Object.assign({}, response)
Studio.addExistingEntity(newTOCTemplate)
} catch (e) {
setProcessing(false)
setError(`Add new template "${templateName}" error: ${e.message}`)
return
}
try {
const updateChanges = {
pdfOperations: [...(mainTemplate.pdfOperations || []), {
type: 'merge',
templateShortid: newTOCTemplate.shortid,
mergeWholeDocument: true
}]
}
updateChanges.content = `${getDefaultTOCContentForMainTemplate()}\n${(mainTemplate.content || '')}`
updateChanges.helpers = `${getDefaultTOCHelpersForMainTemplate()}\n${(mainTemplate.helpers || '')}`
await Studio.updateEntity({
_id: mainTemplate._id,
...updateChanges
})
await Studio.saveEntity(mainTemplate._id)
} catch (e) {
setProcessing(false)
setError(`Update template "${mainTemplate.name}" error: ${e.message}`)
return
}
await Studio.openTab(newTOCTemplate)
close()
}, [processing, templateName, mainTemplate])
return (
<div>
<div>
<h3>Add Table of Contents</h3>
</div>
<div className='form-group' style={{ marginLeft: '0px', marginRight: '0px' }}>
<span style={{ color: 'red', display: error ? 'block' : 'none' }}>{error}</span>
</div>
<div className='form-group'>
<label>Template</label>
<input
type='text'
placeholder='name...'
ref={nameInputRef}
value={templateName}
onChange={(e) => setTemplateName(e.target.value)}
/>
</div>
<div className='button-bar'>
<button className='button danger' onClick={() => close()}>
Cancel
</button>
<button className='button confirmation' onClick={() => handleSave()}>
Ok
</button>
</div>
</div>
)
}
class AddTOCModal extends Component {
render () {
return (
<Content {...this.props} />
)
}
}
function getDefaultTOCContent () {
return (
`<style>
html, body {
margin: 0px;
padding: 0px;
/*this disables the scrollbars of page thus making the available with the same on both renders*/
/*this is needed to have the TOC page at the same position than the main template.*/
overflow: hidden;
}
.toc-title {
margin-top: 0px;
text-align: center;
font-size: 24px;
font-family: arial;
}
.toc-content {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px dashed rgb(200, 200, 200);
}
.toc span {
font-size: 16px;
text-align: right;
}
.toc li {
list-style: none;
padding-bottom: 5px;
}
.toc ul {
font-size: 20px;
font-family: arial;
}
.toc ul ul,
.toc ul ul span {
font-size: 0.8em;
}
.toc ul {
padding-left: 0em;
}
.toc ul ul {
padding-left: 15px;
}
.toc a {
text-decoration: none;
color: black;
}
.toc-no-border {
border-bottom-color: transparent;
}
.toc-hidden {
visibility: hidden;
}
</style>
<div class='toc'>
<h1 class='toc-title {{{addClassOnPdfMerge @root "toc-hidden"}}}'>Table of contents</h1>
<ul>
{{#level (getChapters) null}}
<li>
<div class='toc-content {{{addClassOnPdfMerge @root "toc-no-border"}}}'>
<a class='{{{addClassOnPdfMerge @root "toc-hidden"}}}' href='#{{id}}' data-pdf-outline data-pdf-outline-parent='{{parent}}'>
{{title}}
</a>
<span>{{getPage id}}</span>
</div>
</li>
{{/level}}
</ul>
</div>
`
)
}
function getDefaultTOCHelpers () {
return (
`function addClassOnPdfMerge(root, c) {
if (root.$pdf) {
return c
}
return ''
}
function level(chapters, parent, opts) {
let res = ''
for (const ch of chapters) {
res += opts.fn({
...ch,
parent
})
if (ch.chapters) {
res += '<ul>' + level(ch.chapters, ch.id, opts) + '</ul>'
}
}
return res
}
function getPage(root, id) {
if (!root.$pdf) {
// the main template
return ''
}
for (let i = 0; i < root.$pdf.pages.length; i++) {
const item = root.$pdf.pages[i].items.find(item => item.id === id)
if (item) {
return i + 1
}
}
}
function getChapters() {
// NOTE: replace this and return something based on your real data
return [{
"id": "1",
"title": "The Song of the Bow",
"chapters": [{
"id": "4",
"title": "A Forgotten Tale"
}, {
"id": "5",
"title": "Pennarry Mine"
}]
}, {
"id": "2",
"title": "Cremona"
}, {
"id": "3",
"title": "The Storming Party"
}]
}
`
)
}
function getDefaultTOCContentForMainTemplate () {
return (
`<!--
TOC
-----------
we render TOC template twice
the first in the main template - this is needed for inner pdf links
the second time it is rendered as merge operation in pdf utils - this is needed to for gettign page numbers in TOC
-->
{#child ./toc .recipe=html}
{{#level (getChapters)}}
<div style='page-break-before: always;'></div>
<h1 id="{{id}}">{{title}}</h1>
<!-- pdf utils page item is used for getting page numbers in TOC template -->
{{{pdfAddPageItem id=id}}}
{{/level}}
<!-- END TOC -->
`
)
}
function getDefaultTOCHelpersForMainTemplate () {
return (
`function getChapters() {
// NOTE: replace this and return something based on your real data
return [{
"id": "1",
"title": "The Song of the Bow",
"chapters": [{
"id": "4",
"title": "A Forgotten Tale"
}, {
"id": "5",
"title": "Pennarry Mine"
}]
}, {
"id": "2",
"title": "Cremona"
}, {
"id": "3",
"title": "The Storming Party"
}]
}
function level(chapters, opts) {
let res = ''
for (const ch of chapters) {
res += opts.fn(ch)
if (ch.chapters) {
res += level(ch.chapters, opts)
}
}
return res
}
`
)
}
export default AddTOCModal