Overhauled new gcodefile.
This commit is contained in:
parent
69c8ecd23a
commit
28422018cd
@ -1,554 +1,89 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { useState, useContext, useEffect } from 'react'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import axios from 'axios'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import WizardView from '../../common/WizardView'
|
||||||
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>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
const NewGCodeFile = ({ onOk }) => {
|
||||||
return (
|
return (
|
||||||
<Flex gap={'middle'}>
|
<NewObjectForm
|
||||||
{!isMobile && (
|
type={'gcodeFile'}
|
||||||
<div style={{ minWidth: '160px' }}>
|
defaultValues={{
|
||||||
<Steps
|
state: { type: 'draft' }
|
||||||
current={currentStep}
|
}}
|
||||||
items={steps}
|
>
|
||||||
direction='vertical'
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
style={{ width: 'fit-content' }}
|
const steps = [
|
||||||
/>
|
{
|
||||||
</div>
|
title: 'Upload',
|
||||||
)}
|
key: 'uplaod',
|
||||||
|
content: (
|
||||||
{!isMobile && <Divider type={'vertical'} style={{ height: 'unset' }} />}
|
<ObjectInfo
|
||||||
|
type='gcodeFile'
|
||||||
<Flex vertical={'true'} style={{ flexGrow: 1 }} gap='middle'>
|
column={1}
|
||||||
<Title level={2} style={{ marginTop: 0, marginBottom: 4 }}>
|
bordered={false}
|
||||||
New G Code File
|
isEditing={true}
|
||||||
</Title>
|
required={true}
|
||||||
<Form
|
objectData={objectData}
|
||||||
name='basic'
|
visibleProperties={{ name: false, filament: false }}
|
||||||
autoComplete='off'
|
showLabels={false}
|
||||||
form={newGCodeFileForm}
|
/>
|
||||||
onFinish={handleNewGCodeFile}
|
)
|
||||||
onValuesChange={(changedValues) =>
|
},
|
||||||
setNewGCodeFileFormValues((prevValues) => ({
|
{
|
||||||
...prevValues,
|
title: 'Required',
|
||||||
...changedValues
|
key: 'required',
|
||||||
}))
|
content: (
|
||||||
}
|
<ObjectInfo
|
||||||
initialValues={initialNewGCodeFileForm}
|
type='gcodeFile'
|
||||||
>
|
column={1}
|
||||||
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
<Flex justify={'end'}>
|
required={true}
|
||||||
<Button
|
objectData={objectData}
|
||||||
style={{
|
visibleProperties={{ file: false }}
|
||||||
margin: '0 8px'
|
/>
|
||||||
}}
|
)
|
||||||
onClick={() => {
|
},
|
||||||
setCurrentStep(currentStep - 1)
|
{
|
||||||
setNextEnabled(true)
|
title: 'Summary',
|
||||||
}}
|
key: 'summary',
|
||||||
disabled={!(currentStep > 0)}
|
content: (
|
||||||
>
|
<ObjectInfo
|
||||||
Previous
|
type='gcodeFile'
|
||||||
</Button>
|
column={1}
|
||||||
{currentStep < steps.length - 1 && (
|
bordered={false}
|
||||||
<Button
|
visibleProperties={{
|
||||||
type='primary'
|
_id: false,
|
||||||
disabled={!nextEnabled}
|
createdAt: false,
|
||||||
loading={nextLoading}
|
updatedAt: false,
|
||||||
onClick={() => {
|
startedAt: false
|
||||||
setCurrentStep(currentStep + 1)
|
|
||||||
setNextEnabled(false)
|
|
||||||
}}
|
}}
|
||||||
>
|
isEditing={false}
|
||||||
Next
|
objectData={objectData}
|
||||||
</Button>
|
/>
|
||||||
)}
|
)
|
||||||
{currentStep === steps.length - 1 && (
|
}
|
||||||
<Button
|
]
|
||||||
type='primary'
|
return (
|
||||||
htmlType='submit'
|
<WizardView
|
||||||
loading={newGCodeFileLoading}
|
steps={steps}
|
||||||
>
|
loading={submitLoading}
|
||||||
Done
|
formValid={formValid}
|
||||||
</Button>
|
title='New GCode File'
|
||||||
)}
|
onSubmit={() => {
|
||||||
</Flex>
|
handleSubmit()
|
||||||
</Form>
|
onOk()
|
||||||
</Flex>
|
}}
|
||||||
</Flex>
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
NewGCodeFile.propTypes = {
|
NewGCodeFile.propTypes = {
|
||||||
reset: PropTypes.bool.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
onOk: PropTypes.func.isRequired
|
reset: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewGCodeFile
|
export default NewGCodeFile
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user