jsreport-studio
Version:
jsreport templates editor and designer
234 lines (189 loc) • 6.08 kB
JavaScript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import shortid from 'shortid'
import {
subscribeToThemeChange,
registerPreviewFrameChangeHandler,
registerPreviewConfigurationHandler,
previewFrameChangeHandler,
previewConfigurationHandler
} from '../../lib/configuration.js'
import styles from './Preview.scss'
class Preview extends Component {
static instances = {}
static propTypes = {
onLoad: PropTypes.func
}
constructor (props) {
super(props)
this.containerRef = React.createRef()
this.overlayRef = React.createRef()
this.iframeRef = React.createRef()
this.state = {
nodeKey: shortid.generate(),
src: this.props.initialSrc,
disableTheming: false
}
this.lastURLBlobCreated = null
this.handleOnLoad = this.handleOnLoad.bind(this)
this.applyStylesToIframe = this.applyStylesToIframe.bind(this)
}
componentDidMount () {
this.instanceId = shortid.generate()
Preview.instances[this.instanceId] = this
if (this.props.main) {
this.disposePreviewChangeHandler = registerPreviewFrameChangeHandler((src, opts = {}) => {
let srcToUse = src
const dataURLMatch = /^data:([^,;]+)?(;[^,]+)?(,.+)/.exec(src)
if (dataURLMatch != null) {
const blob = new Blob([decodeURI(dataURLMatch[3].slice(1))], { type: dataURLMatch[1] })
srcToUse = window.URL.createObjectURL(blob)
}
previewConfigurationHandler({ ...opts, src: srcToUse }).then(() => {
if (dataURLMatch != null) {
this.lastURLBlobCreated = srcToUse
}
})
})
this.disposePreviewConfigurationHandler = registerPreviewConfigurationHandler(async (opts = {}) => {
// removing lastURLBlob on each execution
if (this.lastURLBlobCreated != null) {
window.URL.revokeObjectURL(this.lastURLBlobCreated)
this.lastURLBlobCreated = null
}
const newState = {}
if (opts.src == null) {
newState.src = null
} else {
newState.src = opts.src
}
if (opts.disableTheming === true) {
newState.disableTheming = true
} else {
newState.disableTheming = false
}
return new Promise((resolve) => {
this.setState(newState, () => {
resolve()
})
})
})
}
this.unsubscribeThemeChange = subscribeToThemeChange(this.applyStylesToIframe)
}
componentDidUpdate (prevProps, prevState) {
if (prevState.src !== this.state.src && this.lastURLBlobCreated != null) {
window.URL.revokeObjectURL(this.lastURLBlobCreated)
this.lastURLBlobCreated = null
}
}
componentWillUnmount () {
if (this.disposePreviewChangeHandler) {
this.disposePreviewChangeHandler()
}
if (this.disposePreviewConfigurationHandler) {
this.disposePreviewConfigurationHandler()
}
if (this.unsubscribeThemeChange) {
this.unsubscribeThemeChange()
}
if (this.lastURLBlobCreated != null) {
window.URL.revokeObjectURL(this.lastURLBlobCreated)
this.lastURLBlobCreated = null
}
delete Preview.instances[this.instanceId]
}
handleOnLoad () {
this.applyStylesToIframe()
if (this.props.onLoad) {
this.props.onLoad()
}
}
applyStylesToIframe () {
if (!this.containerRef.current || !this.iframeRef.current) {
return
}
try {
const { disableTheming } = this.state
if (this.containerRef.current.classList.contains(styles.containerDefaultBackground)) {
this.containerRef.current.classList.remove(styles.containerDefaultBackground)
}
const previousStyle = this.iframeRef.current.contentDocument.head.querySelector('style[data-jsreport-theme-styles]')
if (previousStyle) {
previousStyle.remove()
}
if (disableTheming) {
this.containerRef.current.classList.add(styles.containerDefaultBackground)
return
}
const containerStyles = window.getComputedStyle(this.containerRef.current, null)
const style = document.createElement('style')
style.dataset.jsreportThemeStyles = true
style.type = 'text/css'
style.appendChild(document.createTextNode(`
html, body {
background-color: ${containerStyles.getPropertyValue('background-color')};
color: ${containerStyles.getPropertyValue('color')};
}
`))
this.iframeRef.current.contentDocument.head.insertBefore(
style,
this.iframeRef.current.contentDocument.head.firstChild
)
} catch (e) {
// ignore error, because it was just cross-origin issues
}
}
changeSrc (newSrc, opts = {}) {
previewFrameChangeHandler(newSrc, opts)
}
clear () {
this.setState({
nodeKey: shortid.generate(),
src: null,
disableTheming: false
})
}
resizeStarted () {
if (this.overlayRef.current) {
this.overlayRef.current.style.display = 'block'
}
if (this.iframeRef.current) {
this.iframeRef.current.style.display = 'none'
}
}
resizeEnded () {
if (this.overlayRef.current) {
this.overlayRef.current.style.display = 'none'
}
if (this.iframeRef.current) {
this.iframeRef.current.style.display = 'block'
}
}
render () {
const { nodeKey, src } = this.state
let mainProps = {}
if (this.props.main) {
mainProps.id = 'preview'
mainProps.name = 'previewFrame'
}
return (
<div ref={this.containerRef} className={`block ${styles.container}`}>
<div ref={this.overlayRef} style={{ display: 'none' }} />
<iframe
key={nodeKey}
ref={this.iframeRef}
frameBorder='0'
onLoad={this.handleOnLoad}
allowFullScreen
width='100%'
height='100%'
src={src == null ? 'about:blank' : src}
className='block-item'
{...mainProps}
/>
</div>
)
}
}
export default Preview