Overhauled new gcodefile.

This commit is contained in:
Tom Butcher 2025-09-07 19:45:57 +01:00
parent 69c8ecd23a
commit 28422018cd

View File

@ -1,554 +1,89 @@
import PropTypes from 'prop-types'
import { useState, useContext, useEffect } from 'react'
import axios from 'axios'
import { useMediaQuery } from 'react-responsive'
import {
capitalizeFirstLetter,
timeStringToMinutes
} from '../../utils/Utils.js'
import {
Form,
Input,
Button,
message,
Typography,
Flex,
Steps,
Divider,
Upload,
Descriptions,
Checkbox,
Spin,
InputNumber,
Badge
} from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { AuthContext } from '../../context/AuthContext.jsx'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import FilamentSelect from '../../common/FilamentSelect'
import config from '../../../../config.js'
const { Dragger } = Upload
const { Title } = Typography
const initialNewGCodeFileForm = {
gcodeFileInfo: {},
name: '',
printTimeMins: 0,
cost: 0,
file: null,
filament: null
}
//const chunkSize = 5000
const NewGCodeFile = ({ onOk, reset }) => {
const [messageApi] = message.useMessage()
const isMobile = useMediaQuery({ maxWidth: 768 })
const [newGCodeFileLoading, setNewGCodeFileLoading] = useState(false)
const [gcodeParsing, setGcodeParsing] = useState(false)
const [filamentSelectFilter, setFilamentSelectFilter] = useState(null)
const [useFilamentSelectFilter, setUseFilamentSelectFilter] = useState(true)
const [currentStep, setCurrentStep] = useState(0)
const [nextEnabled, setNextEnabled] = useState(false)
const [nextLoading, setNextLoading] = useState(false)
const [newGCodeFileForm] = Form.useForm()
const [newGCodeFileFormValues, setNewGCodeFileFormValues] = useState(
initialNewGCodeFileForm
)
const [gcodeFile, setGCodeFile] = useState(null)
const newGCodeFileFormUpdateValues = Form.useWatch([], newGCodeFileForm)
const { token, authenticated } = useContext(AuthContext)
// eslint-disable-next-line
const fetchFilamentInfo = async () => {
if (!authenticated) {
return
}
if (
newGCodeFileFormValues.filament &&
newGCodeFileFormValues.gcodeFileInfo
) {
try {
setNextLoading(true)
const response = await axios.get(
`${config.backendUrl}/filaments/${newGCodeFileFormValues.filament}`,
{
headers: {
Accept: 'application/json'
},
withCredentials: true // Important for including cookies
}
)
setNextLoading(false)
const price =
(response.data.price / 1000) *
newGCodeFileFormValues.gcodeFileInfo.filament_used_g // convert kg to g and multiply
const printTimeMins = timeStringToMinutes(
newGCodeFileFormValues.gcodeFileInfo
.estimated_printing_time_normal_mode
)
setNewGCodeFileFormValues({
...newGCodeFileFormValues,
price,
printTimeMins
})
} catch (error) {
if (error.response) {
messageApi.error(
'Error fetching filament data:',
error.response.status
)
} else {
messageApi.error(
'An unexpected error occurred. Please try again later.'
)
}
}
}
}
useEffect(() => {
newGCodeFileForm
.validateFields({
validateOnly: true
})
.then(() => setNextEnabled(true))
.catch(() => setNextEnabled(false))
}, [newGCodeFileForm, newGCodeFileFormUpdateValues])
const summaryItems = [
{
key: 'name',
label: 'Name',
children: newGCodeFileFormValues?.name
},
{
key: 'filament',
label: 'Filament',
children:
newGCodeFileFormValues?.filament != null ? (
<>
{newGCodeFileFormValues.filament}
<Badge
text={newGCodeFileFormValues.filament.name}
color={newGCodeFileFormValues.filament.color}
/>
</>
) : (
'n/a'
)
},
{
key: 'cost',
label: 'Cost',
children: '£' + newGCodeFileFormValues?.cost
},
{
key: 'sparse_infill_density',
label: 'Infill Density',
children: newGCodeFileFormValues?.gcodeFileInfo?.sparseInfillDensity
},
{
key: 'sparse_infill_pattern',
label: 'Infill Pattern',
children: capitalizeFirstLetter(
newGCodeFileFormValues?.gcodeFileInfo?.sparseInfillPattern
)
},
{
key: 'layer_height',
label: 'Layer Height',
children: newGCodeFileFormValues?.gcodeFileInfo?.layerHeight + 'mm'
},
{
key: 'filamentType',
label: 'Filament Material',
children: newGCodeFileFormValues?.gcodeFileInfo?.filamentType
},
{
key: 'filamentUsedG',
label: 'Filament Used (g)',
children: newGCodeFileFormValues?.gcodeFileInfo?.filamentUsedG + 'g'
},
{
key: 'filamentVendor',
label: 'Filament Brand',
children: newGCodeFileFormValues?.gcodeFileInfo?.filamentVendor
},
{
key: 'hotendTemperature',
label: 'Hotend Temperature',
children: newGCodeFileFormValues?.gcodeFileInfo?.nozzleTemperature + '°'
},
{
key: 'bedTemperature',
label: 'Bed Temperature',
children: newGCodeFileFormValues?.gcodeFileInfo?.hotPlateTemp + '°'
},
{
key: 'estimated_printing_time_normal_mode',
label: 'Est. Print Time',
children:
newGCodeFileFormValues?.gcodeFileInfo?.estimatedPrintingTimeNormalMode
}
]
useEffect(() => {
if (reset) {
setCurrentStep(0)
newGCodeFileForm.resetFields()
}
}, [reset, newGCodeFileForm])
useEffect(() => {
const filamentCost = newGCodeFileFormValues?.filament?.cost
const gcodeFilamentUsed =
newGCodeFileFormValues?.gcodeFileInfo?.filamentUsedG
if (filamentCost && gcodeFilamentUsed) {
const cost = (filamentCost / 1000) * gcodeFilamentUsed
setNewGCodeFileFormValues((prev) => ({ ...prev, cost: cost.toFixed(2) }))
newGCodeFileForm.setFieldValue('cost', cost.toFixed(2))
}
}, [
newGCodeFileForm,
newGCodeFileFormValues?.filament?.cost,
newGCodeFileFormValues?.gcodeFileInfo?.filamentUsedG
])
const handleNewGCodeFileUpload = async (id) => {
setNewGCodeFileLoading(true)
const formData = new FormData()
formData.append('gcodeFile', gcodeFile)
try {
await axios.post(
`${config.backendUrl}/gcodefiles/${id}/content`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${token}`
}
}
)
resetForm()
onOk()
} catch (error) {
messageApi.error('Error creating new gcode file: ' + error.message)
} finally {
setNewGCodeFileLoading(false)
}
}
const handleNewGCodeFile = async () => {
setNewGCodeFileLoading(true)
try {
const request = await axios.post(
`${config.backendUrl}/gcodefiles`,
newGCodeFileFormValues,
{
headers: {
Authorization: `Bearer ${token}`
}
}
)
messageApi.info('New G Code file created successfully. Uploading...')
handleNewGCodeFileUpload(request.data._id)
} catch (error) {
messageApi.error('Error creating new gcode file: ' + error.message)
} finally {
setNewGCodeFileLoading(false)
}
}
const handleGetGCodeFileInfo = async (file) => {
try {
setGcodeParsing(true)
// Create a FormData object to send the file
const formData = new FormData()
formData.append('gcodeFile', file)
// Call the API to extract and parse the config block
const request = await axios.post(
`${config.backendUrl}/gcodefiles/content`,
formData,
{
withCredentials: true // Important for including cookies
},
{
headers: {
Accept: 'application/json'
}
}
)
// Parse the API response
const parsedConfig = await request.data
// Update state with the parsed config from API
setNewGCodeFileFormValues({
...newGCodeFileFormValues,
gcodeFileInfo: parsedConfig
})
// Update filter settings if filament info is available
if (parsedConfig.filament_type && parsedConfig.filament_diameter) {
setFilamentSelectFilter({
type: parsedConfig.filament_type,
diameter: parsedConfig.filament_diameter
})
}
const fileName = file.name.replace(/\.[^/.]+$/, '')
newGCodeFileForm.setFieldValue('name', fileName)
setNewGCodeFileFormValues((prev) => ({
...prev,
name: fileName
}))
setGCodeFile(file)
setGcodeParsing(false)
setCurrentStep(currentStep + 1)
} catch (error) {
console.error('Error getting G-code file info:', error)
}
}
const resetForm = () => {
newGCodeFileForm.setFieldsValue(initialNewGCodeFileForm)
setNewGCodeFileFormValues(initialNewGCodeFileForm)
setGCodeFile(null)
setGcodeParsing(false)
setCurrentStep(0)
}
const steps = [
{
title: 'Upload',
key: 'upload',
content: (
<>
<Form.Item
rules={[
{
required: true,
message: 'Please upload a gcode file.'
}
]}
name='file'
style={{ height: '100%' }}
getValueFromEvent={(e) => (Array.isArray(e) ? e : e && e.fileList)}
>
<Dragger
name='G Code File'
maxCount={1}
showUploadList={false}
customRequest={({ file, onSuccess }) => {
handleGetGCodeFileInfo(file)
setTimeout(() => {
onSuccess('ok')
}, 0)
}}
>
<Flex style={{ height: '100%' }} vertical>
{gcodeParsing == true ? (
<Spin
indicator={
<LoadingOutlined style={{ fontSize: 24 }} spin />
}
/>
) : (
<>
<p className='ant-upload-drag-icon'>
<GCodeFileIcon />
</p>
<p className='ant-upload-text'>
Click or drag gcode file here.
</p>
<p className='ant-upload-hint'>
Supported file extentions: .gcode, .gco, .g
</p>
</>
)}
</Flex>
</Dragger>
</Form.Item>
</>
)
},
{
title: 'Details',
key: 'details',
content: (
<>
<Form.Item
label='Name'
name='name'
rules={[
{
required: true,
message: 'Please enter a name.'
}
]}
>
<Input />
</Form.Item>
<Flex gap={'middle'}>
<Form.Item
label='Filament'
name='filament'
style={{ width: '100%' }}
rules={[
{
required: true,
message: 'Please provide a materal.'
}
]}
>
<FilamentSelect
filter={filamentSelectFilter}
useFilter={useFilamentSelectFilter}
/>
</Form.Item>
<Form.Item>
<Checkbox
checked={useFilamentSelectFilter}
onChange={(e) => {
setUseFilamentSelectFilter(e.target.checked)
}}
>
Filter
</Checkbox>
</Form.Item>
</Flex>
<Form.Item
label='Cost'
name='cost'
rules={[
{
required: true,
message: 'Please enter a cost.'
}
]}
>
<InputNumber
controls={false}
addonBefore='£'
step={0.01}
style={{ width: '100%' }}
readOnly
/>
</Form.Item>
</>
)
},
{
title: 'Summary',
key: 'done',
content: (
<>
<Form.Item>
<Descriptions column={1} items={summaryItems} size={'small'} />
</Form.Item>
</>
)
}
]
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewGCodeFile = ({ onOk }) => {
return (
<Flex gap={'middle'}>
{!isMobile && (
<div style={{ minWidth: '160px' }}>
<Steps
current={currentStep}
items={steps}
direction='vertical'
style={{ width: 'fit-content' }}
/>
</div>
)}
{!isMobile && <Divider type={'vertical'} style={{ height: 'unset' }} />}
<Flex vertical={'true'} style={{ flexGrow: 1 }} gap='middle'>
<Title level={2} style={{ marginTop: 0, marginBottom: 4 }}>
New G Code File
</Title>
<Form
name='basic'
autoComplete='off'
form={newGCodeFileForm}
onFinish={handleNewGCodeFile}
onValuesChange={(changedValues) =>
setNewGCodeFileFormValues((prevValues) => ({
...prevValues,
...changedValues
}))
}
initialValues={initialNewGCodeFileForm}
>
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
<Flex justify={'end'}>
<Button
style={{
margin: '0 8px'
}}
onClick={() => {
setCurrentStep(currentStep - 1)
setNextEnabled(true)
}}
disabled={!(currentStep > 0)}
>
Previous
</Button>
{currentStep < steps.length - 1 && (
<Button
type='primary'
disabled={!nextEnabled}
loading={nextLoading}
onClick={() => {
setCurrentStep(currentStep + 1)
setNextEnabled(false)
<NewObjectForm
type={'gcodeFile'}
defaultValues={{
state: { type: 'draft' }
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Upload',
key: 'uplaod',
content: (
<ObjectInfo
type='gcodeFile'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{ name: false, filament: false }}
showLabels={false}
/>
)
},
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='gcodeFile'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{ file: false }}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='gcodeFile'
column={1}
bordered={false}
visibleProperties={{
_id: false,
createdAt: false,
updatedAt: false,
startedAt: false
}}
>
Next
</Button>
)}
{currentStep === steps.length - 1 && (
<Button
type='primary'
htmlType='submit'
loading={newGCodeFileLoading}
>
Done
</Button>
)}
</Flex>
</Form>
</Flex>
</Flex>
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New GCode File'
onSubmit={() => {
handleSubmit()
onOk()
}}
/>
)
}}
</NewObjectForm>
)
}
NewGCodeFile.propTypes = {
reset: PropTypes.bool.isRequired,
onOk: PropTypes.func.isRequired
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool
}
export default NewGCodeFile