- Introduced PrinterGCodeFilePanel component to display G-code file information and loading progress. - Updated ControlPrinter component to include GCode File section with collapsible panel for G-code file management. - Enhanced GCodePreview component to support following the current sub-job layer during G-code visualization. - Added logic to handle G-code position updates and improve user experience during file loading and display.
205 lines
5.2 KiB
JavaScript
205 lines
5.2 KiB
JavaScript
import { useContext, useEffect, useRef, useState } from 'react'
|
|
import { Card, Flex, Spin } from 'antd'
|
|
import { LoadingOutlined } from '@ant-design/icons'
|
|
import PropTypes from 'prop-types'
|
|
import merge from 'lodash/merge'
|
|
import { ApiServerContext } from '../context/ApiServerContext'
|
|
import { AuthContext } from '../context/AuthContext'
|
|
import GCodePreview from './GCodePreview'
|
|
import MissingPlaceholder from './MissingPlaceholder'
|
|
import ProgressDisplay from './ProgressDisplay'
|
|
|
|
const emptyGcodePosition = {
|
|
position: [0, 0, 0, 0],
|
|
filePosition: 0,
|
|
fileSize: 0,
|
|
progress: 0,
|
|
isActive: false,
|
|
currentLayer: 0,
|
|
totalLayer: 0
|
|
}
|
|
|
|
const PrinterGCodeFilePanel = ({ id, subJobId, offline = true }) => {
|
|
const { subscribeToObjectEvent, connected, fetchObject, fetchFileContent } =
|
|
useContext(ApiServerContext)
|
|
const { token } = useContext(AuthContext)
|
|
const apiRef = useRef({ fetchObject, fetchFileContent })
|
|
apiRef.current = { fetchObject, fetchFileContent }
|
|
const objectUrlRef = useRef(null)
|
|
|
|
const [gcodePosition, setGcodePosition] = useState(emptyGcodePosition)
|
|
const [src, setSrc] = useState(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
const [downloadProgress, setDownloadProgress] = useState(null)
|
|
|
|
useEffect(() => {
|
|
if (offline) {
|
|
setGcodePosition(emptyGcodePosition)
|
|
}
|
|
}, [offline])
|
|
|
|
useEffect(() => {
|
|
if (id && connected == true && offline == false) {
|
|
const gcodePositionUnsubscribe = subscribeToObjectEvent(
|
|
id?.toLowerCase(),
|
|
'printer',
|
|
'gcodePosition',
|
|
(event) => {
|
|
setGcodePosition((prev) => merge({}, prev, event.data))
|
|
}
|
|
)
|
|
return () => {
|
|
if (gcodePositionUnsubscribe) gcodePositionUnsubscribe()
|
|
}
|
|
}
|
|
}, [id, connected, subscribeToObjectEvent, offline])
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
|
|
const replaceSrc = (nextSrc) => {
|
|
if (objectUrlRef.current && objectUrlRef.current !== nextSrc) {
|
|
URL.revokeObjectURL(objectUrlRef.current)
|
|
}
|
|
objectUrlRef.current = nextSrc
|
|
setSrc(nextSrc)
|
|
}
|
|
|
|
const loadGcodeFile = async () => {
|
|
replaceSrc(null)
|
|
setError(null)
|
|
if (!subJobId || token == null) {
|
|
setLoading(false)
|
|
setDownloadProgress(null)
|
|
return
|
|
}
|
|
|
|
setLoading(true)
|
|
setDownloadProgress({ progress: 0, message: 'Loading G-code file...' })
|
|
const subJob = await apiRef.current.fetchObject(subJobId, 'subJob')
|
|
if (cancelled) return
|
|
const gcodeFileId = subJob?.gcodeFile?._id
|
|
if (!gcodeFileId) {
|
|
setLoading(false)
|
|
setDownloadProgress(null)
|
|
setError('No G-code file.')
|
|
return
|
|
}
|
|
|
|
const gcodeFile = await apiRef.current.fetchObject(
|
|
gcodeFileId,
|
|
'gcodeFile'
|
|
)
|
|
if (cancelled) return
|
|
const file = gcodeFile?.file
|
|
if (!file?._id) {
|
|
setLoading(false)
|
|
setDownloadProgress(null)
|
|
setError('No G-code file.')
|
|
return
|
|
}
|
|
|
|
const objectUrl = await apiRef.current.fetchFileContent(
|
|
file,
|
|
false,
|
|
(progress) => {
|
|
if (cancelled) return
|
|
setDownloadProgress(progress)
|
|
}
|
|
)
|
|
if (cancelled) {
|
|
if (objectUrl) URL.revokeObjectURL(objectUrl)
|
|
return
|
|
}
|
|
if (!objectUrl) {
|
|
setLoading(false)
|
|
setDownloadProgress(null)
|
|
setError('Failed to load G-code file.')
|
|
return
|
|
}
|
|
replaceSrc(objectUrl)
|
|
setDownloadProgress(null)
|
|
setLoading(false)
|
|
}
|
|
|
|
loadGcodeFile()
|
|
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [subJobId, token])
|
|
|
|
useEffect(() => {
|
|
return () => {
|
|
if (objectUrlRef.current) {
|
|
URL.revokeObjectURL(objectUrlRef.current)
|
|
objectUrlRef.current = null
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
if (!subJobId) {
|
|
return (
|
|
<MissingPlaceholder
|
|
message={'No sub job.'}
|
|
padding='24px'
|
|
hasBackground={false}
|
|
mutedBorder={true}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<MissingPlaceholder
|
|
message={error}
|
|
padding='24px'
|
|
hasBackground={false}
|
|
mutedBorder={true}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
{loading || !src ? (
|
|
<Flex align='center' justify='center' style={{ minHeight: 240 }}>
|
|
{downloadProgress ? (
|
|
<div style={{ width: '100%', maxWidth: 360 }}>
|
|
<ProgressDisplay
|
|
percent={Math.round(
|
|
(Number(downloadProgress.progress) || 0) * 100
|
|
)}
|
|
status='active'
|
|
>
|
|
{downloadProgress.message || 'Downloading...'}
|
|
</ProgressDisplay>
|
|
</div>
|
|
) : (
|
|
<Spin indicator={<LoadingOutlined spin />} size='large' />
|
|
)}
|
|
</Flex>
|
|
) : (
|
|
<GCodePreview
|
|
src={src}
|
|
startLayer={0}
|
|
endLayer={undefined}
|
|
lineWidth={1}
|
|
showFollowCurrentSubJob={true}
|
|
gcodePosition={gcodePosition}
|
|
style={{ width: '100%', height: '60vh' }}
|
|
/>
|
|
)}
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
PrinterGCodeFilePanel.propTypes = {
|
|
id: PropTypes.string,
|
|
subJobId: PropTypes.string,
|
|
offline: PropTypes.bool
|
|
}
|
|
|
|
export default PrinterGCodeFilePanel
|