234 lines
10 KiB
TypeScript
234 lines
10 KiB
TypeScript
import { IconHelp, IconWindowMaximize, IconWindowMinimize } from '@tabler/icons-react'
|
||
import React, { useEffect, useRef, useState } from 'react'
|
||
import { clearPrintArea, PrintScale, setPreviousView, setPrintScale, setPrintScaleLine, useMapStore } from '../../../store/map'
|
||
import { PrintFormat, PrintOrientation, printResolutions, setPrintOrientation, setPrintResolution, usePrintStore } from '../../../store/print'
|
||
import { printDimensions, scaleOptions } from '../../../constants/map'
|
||
import { useObjectsStore } from '../../../store/objects'
|
||
import jsPDF from 'jspdf'
|
||
import { getCenter } from 'ol/extent'
|
||
import ScaleLine from 'ol/control/ScaleLine'
|
||
import { Button, Checkbox, Dialog, DialogActions, DialogBody, DialogContent, DialogSurface, DialogTitle, Dropdown, Field, Option, Radio, RadioGroup } from '@fluentui/react-components'
|
||
import { Dismiss24Regular } from '@fluentui/react-icons'
|
||
|
||
const MapPrint = ({
|
||
id,
|
||
mapElement
|
||
}: {
|
||
id: string
|
||
mapElement: React.MutableRefObject<HTMLDivElement | null>
|
||
}) => {
|
||
const [fullscreen, setFullscreen] = useState(false)
|
||
const { printOrientation, printResolution, printFormat } = usePrintStore()
|
||
const { selectedYear, selectedRegion, selectedDistrict } = useObjectsStore().id[id]
|
||
|
||
const {
|
||
map,
|
||
mode,
|
||
previousView, printArea, printSource, printAreaDraw, printScale, printScaleLine,
|
||
} = useMapStore().id[id]
|
||
|
||
const exportToPDF = (format: PrintFormat, resolution: number, orientation: PrintOrientation) => {
|
||
const dim = printDimensions[format]
|
||
|
||
const width = Math.round((dim[orientation === 'horizontal' ? 0 : 1] * resolution) / 25.4)
|
||
const height = Math.round((dim[orientation === 'horizontal' ? 1 : 0] * resolution) / 25.4)
|
||
|
||
if (!map) return
|
||
|
||
// Store original size and scale
|
||
const originalSize = map.getSize()
|
||
const originalResolution = map.getView().getResolution()
|
||
|
||
if (!originalSize || !originalResolution) return
|
||
|
||
// Calculate new resolution to fit high DPI
|
||
const scaleFactor = width / originalSize[0]
|
||
const newResolution = originalResolution / scaleFactor
|
||
|
||
// Set new high-resolution rendering
|
||
map.setSize([width, height])
|
||
map.getView().setResolution(newResolution)
|
||
map.renderSync()
|
||
|
||
map.once("rendercomplete", function () {
|
||
const mapCanvas = document.createElement("canvas")
|
||
mapCanvas.width = width
|
||
mapCanvas.height = height
|
||
const mapContext = mapCanvas.getContext("2d")
|
||
|
||
if (!mapContext) return
|
||
|
||
const canvas = document.querySelector('canvas')
|
||
if (canvas) {
|
||
if (canvas.width > 0) {
|
||
const opacity = canvas.parentElement?.style.opacity || "1"
|
||
mapContext.globalAlpha = parseFloat(opacity)
|
||
|
||
const transform = canvas.style.transform
|
||
const matrixMatch = transform.match(/^matrix\(([^)]+)\)$/)
|
||
if (matrixMatch) {
|
||
const matrix = matrixMatch[1].split(",").map(Number)
|
||
mapContext.setTransform(...matrix as [number, number, number, number, number, number])
|
||
}
|
||
|
||
mapContext.drawImage(canvas, 0, 0)
|
||
}
|
||
}
|
||
|
||
mapContext.globalAlpha = 1
|
||
mapContext.setTransform(1, 0, 0, 1, 0, 0)
|
||
|
||
// Restore original map settings
|
||
map.setSize(originalSize)
|
||
map.getView().setResolution(originalResolution)
|
||
map.renderSync()
|
||
|
||
const dimensions = {
|
||
w: orientation === 'horizontal' ? dim[0] : dim[1],
|
||
h: orientation === 'horizontal' ? dim[1] : dim[0]
|
||
}
|
||
|
||
// Generate PDF
|
||
const pdf = new jsPDF(orientation === 'horizontal' ? "landscape" : 'portrait', undefined, format)
|
||
pdf.addImage(mapCanvas.toDataURL("image/jpeg"), "JPEG", 0, 0, dimensions.w, dimensions.h)
|
||
|
||
const filename = `${selectedYear}-${selectedRegion}-${selectedDistrict}-${new Date().toISOString()}.pdf`
|
||
pdf.save(filename, {
|
||
returnPromise: true
|
||
}).then(() => {
|
||
|
||
})
|
||
})
|
||
}
|
||
|
||
const scaleLine = useRef(new ScaleLine({
|
||
bar: true,
|
||
text: true,
|
||
minWidth: 125
|
||
}))
|
||
|
||
const [opened, setOpened] = useState(false)
|
||
|
||
useEffect(() => {
|
||
if (printArea && opened) {
|
||
// backup view before entering print mode
|
||
setPreviousView(id, map?.getView())
|
||
|
||
map?.setTarget('print-portal')
|
||
|
||
printSource.clear()
|
||
map?.getView().setCenter(getCenter(printArea))
|
||
map?.getView().fit(printArea, {
|
||
size: printOrientation === 'horizontal' ? [594, 420] : [420, 594]
|
||
})
|
||
map?.removeInteraction(printAreaDraw)
|
||
}
|
||
}, [printArea, map, opened])
|
||
|
||
useEffect(() => {
|
||
if (printScaleLine && printArea) {
|
||
map?.addControl(scaleLine.current)
|
||
} else {
|
||
map?.removeControl(scaleLine.current)
|
||
}
|
||
}, [printScaleLine, printArea])
|
||
|
||
useEffect(() => {
|
||
if (!!printArea) {
|
||
setOpened(true)
|
||
}
|
||
}, [printArea])
|
||
|
||
useEffect(() => {
|
||
if (!opened && mode === 'print') {
|
||
clearPrintArea(id)
|
||
map?.setTarget(mapElement.current as HTMLDivElement)
|
||
map?.addInteraction(printAreaDraw)
|
||
}
|
||
}, [opened])
|
||
|
||
return (
|
||
<Dialog open={opened}>
|
||
<DialogSurface style={{ maxWidth: fullscreen ? '100%' : 'fit-content', maxHeight: fullscreen ? '100%' : 'fit-content' }}>
|
||
<DialogBody>
|
||
<DialogTitle action={
|
||
<div style={{ display: 'flex', marginLeft: 'auto', gap: '1.5rem' }}>
|
||
<Button appearance='subtle' title='Помощь' style={{ marginLeft: 'auto' }} icon={<IconHelp color='gray' />} />
|
||
|
||
<Button appearance='subtle' title={fullscreen ? 'Свернуть' : 'Развернуть'} style={{ marginLeft: 'auto' }} icon={fullscreen ? <IconWindowMinimize color='gray' /> : <IconWindowMaximize color='gray' />} onClick={() => setFullscreen(!fullscreen)} />
|
||
|
||
<Button appearance='subtle' title='Закрыть' icon={<Dismiss24Regular />} onClick={() => setOpened(false)} />
|
||
</div>
|
||
}>Предпросмотр области печати</DialogTitle>
|
||
|
||
<DialogContent style={{ display: 'flex', justifyContent: 'center' }}>
|
||
<div style={{ display: 'flex', width: 'fit-content', flexDirection: 'row', alignItems: 'flex-start', height: 'fit-content', overflowY: 'auto' }}>
|
||
<div id='print-portal' style={{
|
||
width: printOrientation === 'horizontal' ? '594px' : '420px',
|
||
height: printOrientation === 'horizontal' ? '420px' : '594px',
|
||
flexShrink: '0'
|
||
}}>
|
||
|
||
</div>
|
||
|
||
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', flexWrap: 'wrap', gap: '1rem', padding: '1rem', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||
<Field label={'Ориентация'}>
|
||
<RadioGroup value={printOrientation} onChange={(_, data) => setPrintOrientation(data.value as PrintOrientation)}>
|
||
<Radio value='horizontal' label='Горизонтальная' />
|
||
<Radio value='vertical' label='Вертикальная' />
|
||
</RadioGroup>
|
||
</Field>
|
||
|
||
<Field label="Разрешение">
|
||
<Dropdown
|
||
value={printResolution.toString()}
|
||
selectedOptions={[printResolution.toString()]}
|
||
onOptionSelect={(_, data) => setPrintResolution(Number(data.optionValue))}
|
||
>
|
||
{printResolutions.map((res) => (
|
||
<Option key={res} text={res} value={res}>
|
||
{res}
|
||
</Option>
|
||
))}
|
||
</Dropdown>
|
||
</Field>
|
||
|
||
|
||
<Field label="Масштаб">
|
||
<Dropdown
|
||
value={printScale.toString()}
|
||
selectedOptions={[printScale]}
|
||
onOptionSelect={(_, data) => setPrintScale(id, data.optionValue as PrintScale)}
|
||
>
|
||
{scaleOptions.map((opt) => (
|
||
<Option key={opt.value} text={opt.label} value={opt.value}>
|
||
{opt.label}
|
||
</Option>
|
||
))}
|
||
</Dropdown>
|
||
</Field>
|
||
</div>
|
||
</div>
|
||
</DialogContent>
|
||
|
||
<DialogActions>
|
||
<Checkbox
|
||
checked={printScaleLine}
|
||
label="Масштабная линия"
|
||
onChange={(event) => setPrintScaleLine(id, event.currentTarget.checked)}
|
||
/>
|
||
<Button style={{ marginLeft: 'auto' }} onClick={() => {
|
||
if (previousView) {
|
||
exportToPDF(printFormat, printResolution, printOrientation)
|
||
}
|
||
}}>
|
||
Печать
|
||
</Button>
|
||
</DialogActions>
|
||
</DialogBody>
|
||
</DialogSurface>
|
||
</Dialog>
|
||
)
|
||
}
|
||
|
||
export default MapPrint |