420 lines
13 KiB
JavaScript
420 lines
13 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
|
import { useLocation } from 'react-router-dom'
|
|
import axios from 'axios'
|
|
import {
|
|
Descriptions,
|
|
Spin,
|
|
Space,
|
|
Button,
|
|
message,
|
|
Badge,
|
|
Form,
|
|
Typography,
|
|
Flex,
|
|
Input,
|
|
Card,
|
|
Collapse
|
|
} from 'antd'
|
|
import { LoadingOutlined, CaretRightOutlined } from '@ant-design/icons'
|
|
import IdText from '../../common/IdText.jsx'
|
|
import { capitalizeFirstLetter } from '../../utils/Utils.js'
|
|
import FilamentSelect from '../../common/FilamentSelect'
|
|
import useCollapseState from '../../hooks/useCollapseState'
|
|
import FilamentIcon from '../../../Icons/FilamentIcon'
|
|
import TimeDisplay from '../../common/TimeDisplay.jsx'
|
|
import ReloadIcon from '../../../Icons/ReloadIcon'
|
|
import EditIcon from '../../../Icons/EditIcon.jsx'
|
|
import XMarkIcon from '../../../Icons/XMarkIcon.jsx'
|
|
import CheckIcon from '../../../Icons/CheckIcon.jsx'
|
|
|
|
import config from '../../../../config.js'
|
|
|
|
const { Title } = Typography
|
|
|
|
const GCodeFileInfo = () => {
|
|
const [gcodeFileData, setGCodeFileData] = useState(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
const location = useLocation()
|
|
const [messageApi, contextHolder] = message.useMessage()
|
|
const gcodeFileId = new URLSearchParams(location.search).get('gcodeFileId')
|
|
const [isEditing, setIsEditing] = useState(false)
|
|
const [form] = Form.useForm()
|
|
const [fetchLoading, setFetchLoading] = useState(true)
|
|
const [collapseState, updateCollapseState] = useCollapseState(
|
|
'GCodeFileInfo',
|
|
{
|
|
info: true,
|
|
preview: true
|
|
}
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (gcodeFileId) {
|
|
fetchGCodeFileDetails()
|
|
}
|
|
}, [gcodeFileId])
|
|
|
|
useEffect(() => {
|
|
if (gcodeFileData) {
|
|
form.setFieldsValue({
|
|
name: gcodeFileData.name || '',
|
|
filament: gcodeFileData.filament || { id: null, name: '' }
|
|
})
|
|
}
|
|
}, [gcodeFileData, form])
|
|
|
|
const fetchGCodeFileDetails = async () => {
|
|
try {
|
|
setFetchLoading(true)
|
|
const response = await axios.get(
|
|
`${config.backendUrl}/gcodefiles/${gcodeFileId}`,
|
|
{
|
|
headers: {
|
|
Accept: 'application/json'
|
|
},
|
|
withCredentials: true
|
|
}
|
|
)
|
|
setGCodeFileData(response.data)
|
|
setError(null)
|
|
} catch (err) {
|
|
setError('Failed to fetch GCodeFile details')
|
|
messageApi.error('Failed to fetch GCodeFile details')
|
|
} finally {
|
|
setFetchLoading(false)
|
|
}
|
|
}
|
|
|
|
const startEditing = () => {
|
|
setIsEditing(true)
|
|
updateCollapseState('info', true)
|
|
}
|
|
|
|
const cancelEditing = () => {
|
|
form.setFieldsValue({
|
|
name: gcodeFileData?.name || '',
|
|
filament: gcodeFileData?.filament || { id: null, name: '' }
|
|
})
|
|
setIsEditing(false)
|
|
}
|
|
|
|
const updateInfo = async () => {
|
|
try {
|
|
const values = await form.validateFields()
|
|
setLoading(true)
|
|
|
|
await axios.put(
|
|
`${config.backendUrl}/gcodefiles/${gcodeFileId}`,
|
|
values,
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
withCredentials: true
|
|
}
|
|
)
|
|
|
|
setGCodeFileData({ ...gcodeFileData, ...values })
|
|
setIsEditing(false)
|
|
messageApi.success('GCode File information updated successfully')
|
|
} catch (err) {
|
|
if (err.errorFields) {
|
|
return
|
|
}
|
|
console.error('Failed to update gcode file information:', err)
|
|
messageApi.error('Failed to update gcode file information')
|
|
} finally {
|
|
fetchGCodeFileDetails()
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
if (error || !gcodeFileData) {
|
|
return (
|
|
<Space
|
|
direction='vertical'
|
|
style={{ width: '100%', textAlign: 'center' }}
|
|
>
|
|
<p>{error || 'GCodeFile not found'}</p>
|
|
<Button icon={<ReloadIcon />} onClick={fetchGCodeFileDetails}>
|
|
Retry
|
|
</Button>
|
|
</Space>
|
|
)
|
|
}
|
|
|
|
if (fetchLoading) {
|
|
return (
|
|
<div style={{ textAlign: 'center', padding: '20px' }}>
|
|
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}>
|
|
{contextHolder}
|
|
<Flex vertical gap={'large'}>
|
|
<Collapse
|
|
ghost
|
|
collapsible='icon'
|
|
activeKey={collapseState.info ? ['1'] : []}
|
|
onChange={(keys) => updateCollapseState('info', keys.length > 0)}
|
|
expandIcon={({ isActive }) => (
|
|
<CaretRightOutlined
|
|
rotate={isActive ? 90 : 0}
|
|
style={{ paddingTop: '9px' }}
|
|
/>
|
|
)}
|
|
className='no-h-padding-collapse no-t-padding-collapse'
|
|
>
|
|
<Collapse.Panel
|
|
header={
|
|
<Flex
|
|
align='center'
|
|
justify='space-between'
|
|
style={{ width: '100%' }}
|
|
>
|
|
<Title level={5} style={{ margin: 0 }}>
|
|
GCode File Information
|
|
</Title>
|
|
<Space>
|
|
{isEditing ? (
|
|
<>
|
|
<Button
|
|
icon={<CheckIcon />}
|
|
type='primary'
|
|
onClick={updateInfo}
|
|
loading={loading}
|
|
/>
|
|
<Button
|
|
icon={<XMarkIcon />}
|
|
onClick={cancelEditing}
|
|
disabled={loading}
|
|
/>
|
|
</>
|
|
) : (
|
|
<Button icon={<EditIcon />} onClick={startEditing} />
|
|
)}
|
|
</Space>
|
|
</Flex>
|
|
}
|
|
key='1'
|
|
>
|
|
<Form form={form} layout='vertical'>
|
|
<Descriptions
|
|
bordered
|
|
column={{
|
|
xs: 1,
|
|
sm: 1,
|
|
md: 1,
|
|
lg: 2,
|
|
xl: 2,
|
|
xxl: 2
|
|
}}
|
|
>
|
|
<Descriptions.Item label='ID' span={1}>
|
|
{gcodeFileData.id ? (
|
|
<IdText id={gcodeFileData.id} type='gcodeFile'></IdText>
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Created At'>
|
|
<TimeDisplay
|
|
dateTime={gcodeFileData.createdAt}
|
|
showSince={true}
|
|
/>
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Name'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='name'
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: 'Please enter a vendor name'
|
|
},
|
|
{
|
|
max: 100,
|
|
message: 'Name cannot exceed 100 characters'
|
|
}
|
|
]}
|
|
style={{ margin: 0 }}
|
|
>
|
|
<Input />
|
|
</Form.Item>
|
|
) : (
|
|
gcodeFileData.name
|
|
)}
|
|
</Descriptions.Item>
|
|
|
|
<Descriptions.Item label='Updated At'>
|
|
<TimeDisplay
|
|
dateTime={gcodeFileData.updatedAt}
|
|
showSince={true}
|
|
/>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Filament Name'>
|
|
{isEditing ? (
|
|
<Form.Item
|
|
name='filament'
|
|
rules={[
|
|
{ required: true, message: 'Please enter a filament' }
|
|
]}
|
|
style={{ margin: 0 }}
|
|
>
|
|
<FilamentSelect />
|
|
</Form.Item>
|
|
) : gcodeFileData.filament ? (
|
|
<Space>
|
|
<FilamentIcon />
|
|
<Badge
|
|
color={gcodeFileData.filament.color}
|
|
text={gcodeFileData.filament.name}
|
|
/>
|
|
</Space>
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Filament ID'>
|
|
{gcodeFileData.filament ? (
|
|
<IdText
|
|
id={gcodeFileData.filament.id}
|
|
type={'filament'}
|
|
showHyperlink={true}
|
|
/>
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Est Print Time'>
|
|
{gcodeFileData.gcodeFileInfo
|
|
.estimatedPrintingTimeNormalMode || 'n/a'}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Cost'>
|
|
{'£' + gcodeFileData.cost.toFixed(2) || 'n/a'}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Infill Density'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.sparseInfillDensity) {
|
|
return gcodeFileData.gcodeFileInfo.sparseInfillDensity
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Infill Pattern'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.sparseInfillPattern) {
|
|
return capitalizeFirstLetter(
|
|
gcodeFileData.gcodeFileInfo.sparseInfillPattern
|
|
)
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Filament Used (mm)'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.filamentUsedMm) {
|
|
return `${gcodeFileData.gcodeFileInfo.filamentUsedMm}mm`
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Filament Used (g)'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.filamentUsedG) {
|
|
return `${gcodeFileData.gcodeFileInfo.filamentUsedG}g`
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Hotend Temperature'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.nozzleTemperature) {
|
|
return `${gcodeFileData.gcodeFileInfo.nozzleTemperature}°`
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Bed Temperature'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.hotPlateTemp) {
|
|
return `${gcodeFileData.gcodeFileInfo.hotPlateTemp}°`
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Filament Profile'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.filamentSettingsId) {
|
|
return `${gcodeFileData.gcodeFileInfo.filamentSettingsId.replaceAll('"', '')}`
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label='Print Profile'>
|
|
{(() => {
|
|
if (gcodeFileData.gcodeFileInfo.printSettingsId) {
|
|
return `${gcodeFileData.gcodeFileInfo.printSettingsId.replaceAll('"', '')}`
|
|
} else {
|
|
return 'n/a'
|
|
}
|
|
})()}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
</Form>
|
|
</Collapse.Panel>
|
|
</Collapse>
|
|
|
|
<Collapse
|
|
ghost
|
|
collapsible='icon'
|
|
activeKey={collapseState.preview ? ['2'] : []}
|
|
onChange={(keys) => updateCollapseState('preview', keys.length > 0)}
|
|
expandIcon={({ isActive }) => (
|
|
<CaretRightOutlined
|
|
rotate={isActive ? 90 : 0}
|
|
style={{ paddingTop: '2px' }}
|
|
/>
|
|
)}
|
|
className='no-h-padding-collapse'
|
|
>
|
|
<Collapse.Panel
|
|
header={
|
|
<Title level={5} style={{ margin: 0 }}>
|
|
GCode File Preview
|
|
</Title>
|
|
}
|
|
key='2'
|
|
>
|
|
<Card styles={{ body: { padding: '10px' } }}>
|
|
{gcodeFileData.gcodeFileInfo.thumbnail ? (
|
|
<img
|
|
src={`data:image/png;base64,${gcodeFileData.gcodeFileInfo.thumbnail.data}`}
|
|
alt='GCodeFile'
|
|
style={{ maxWidth: '100%' }}
|
|
/>
|
|
) : (
|
|
'n/a'
|
|
)}
|
|
</Card>
|
|
</Collapse.Panel>
|
|
</Collapse>
|
|
</Flex>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default GCodeFileInfo
|