Enhance GCodeFile, DeployJob, NewJob, and ControlPrinter components with slicer integration support
- Updated NewGCodeFile and NewJob components to include slicerIntegration, currentStep, and onStepChange props for improved workflow management. - Modified DeployJob to handle printer online status and added slicerIntegration support for conditional rendering of actions and steps. - Enhanced ControlPrinter to manage visibility of actions based on slicerIntegration, improving user experience in printer control. - Refactored form handling to pass result objects to onOk callbacks, ensuring better data handling upon submission.
This commit is contained in:
parent
def9b9aefb
commit
d7ad2208d4
@ -1,35 +1,49 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import NewObjectForm from '../../common/NewObjectForm'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import WizardView from '../../common/WizardView'
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
const NewGCodeFile = ({ onOk, defaultValues }) => {
|
const NewGCodeFile = ({
|
||||||
|
onOk,
|
||||||
|
defaultValues,
|
||||||
|
slicerIntegration = false,
|
||||||
|
shouldPrint = false,
|
||||||
|
currentStep,
|
||||||
|
onStepChange
|
||||||
|
}) => {
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
state: { type: 'draft' },
|
||||||
|
...defaultValues
|
||||||
|
}),
|
||||||
|
[defaultValues]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm type={'gcodeFile'} defaultValues={initialValues}>
|
||||||
type={'gcodeFile'}
|
|
||||||
defaultValues={{
|
|
||||||
state: { type: 'draft' },
|
|
||||||
...defaultValues
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
...(!slicerIntegration
|
||||||
title: 'Upload',
|
? [
|
||||||
key: 'upload',
|
{
|
||||||
content: (
|
title: 'Upload',
|
||||||
<ObjectInfo
|
key: 'upload',
|
||||||
type='gcodeFile'
|
content: (
|
||||||
column={1}
|
<ObjectInfo
|
||||||
bordered={false}
|
type='gcodeFile'
|
||||||
isEditing={true}
|
column={1}
|
||||||
required={true}
|
bordered={false}
|
||||||
objectData={objectData}
|
isEditing={true}
|
||||||
visibleProperties={{ file: true }}
|
required={true}
|
||||||
showLabels={false}
|
objectData={objectData}
|
||||||
/>
|
visibleProperties={{ file: true }}
|
||||||
)
|
showLabels={false}
|
||||||
},
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
title: 'Required',
|
title: 'Required',
|
||||||
key: 'required',
|
key: 'required',
|
||||||
@ -73,10 +87,13 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
|
|||||||
loading={submitLoading}
|
loading={submitLoading}
|
||||||
formValid={formValid}
|
formValid={formValid}
|
||||||
title='New GCode File'
|
title='New GCode File'
|
||||||
|
showSteps={!slicerIntegration || !shouldPrint}
|
||||||
|
currentStep={currentStep}
|
||||||
|
onStepChange={onStepChange}
|
||||||
onSubmit={async () => {
|
onSubmit={async () => {
|
||||||
const result = await handleSubmit()
|
const result = await handleSubmit()
|
||||||
if (result) {
|
if (result) {
|
||||||
onOk()
|
onOk(result)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -89,7 +106,11 @@ const NewGCodeFile = ({ onOk, defaultValues }) => {
|
|||||||
NewGCodeFile.propTypes = {
|
NewGCodeFile.propTypes = {
|
||||||
onOk: PropTypes.func.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
reset: PropTypes.bool,
|
reset: PropTypes.bool,
|
||||||
defaultValues: PropTypes.object
|
defaultValues: PropTypes.object,
|
||||||
|
slicerIntegration: PropTypes.bool,
|
||||||
|
currentStep: PropTypes.number,
|
||||||
|
onStepChange: PropTypes.func,
|
||||||
|
shouldPrint: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewGCodeFile
|
export default NewGCodeFile
|
||||||
|
|||||||
@ -2,17 +2,20 @@ import { useState, useContext, useEffect, useCallback } from 'react'
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import WizardView from '../../common/WizardView'
|
import WizardView from '../../common/WizardView'
|
||||||
import { ApiServerContext } from '../../context/ApiServerContext'
|
import { ApiServerContext } from '../../context/ApiServerContext'
|
||||||
import { Flex, Typography } from 'antd'
|
import { Descriptions, Flex, Typography } from 'antd'
|
||||||
import ObjectTable from '../../common/ObjectTable'
|
import ObjectTable from '../../common/ObjectTable'
|
||||||
import ObjectSelect from '../../common/ObjectSelect'
|
import ObjectProperty from '../../common/ObjectProperty'
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
const DeployJob = ({
|
||||||
const DeployJob = ({ onOk, objectData = undefined }) => {
|
onOk,
|
||||||
|
objectData = undefined,
|
||||||
|
slicerIntegration = false
|
||||||
|
}) => {
|
||||||
const [deployLoading, setDeployLoading] = useState(false)
|
const [deployLoading, setDeployLoading] = useState(false)
|
||||||
const [job, setJob] = useState(objectData)
|
const [job, setJob] = useState(objectData)
|
||||||
const [deployedSubJobsCount, setDeployedSubJobsCount] = useState(0)
|
const [deployedSubJobsCount, setDeployedSubJobsCount] = useState(0)
|
||||||
const [subJobsCount, setSubJobsCount] = useState(999)
|
const [subJobsCount, setSubJobsCount] = useState(999)
|
||||||
|
const [allPrintersOnline, setAllPrintersOnline] = useState(false)
|
||||||
const { sendObjectAction, fetchObjects } = useContext(ApiServerContext)
|
const { sendObjectAction, fetchObjects } = useContext(ApiServerContext)
|
||||||
|
|
||||||
const handleDeploy = useCallback(async () => {
|
const handleDeploy = useCallback(async () => {
|
||||||
@ -51,21 +54,94 @@ const DeployJob = ({ onOk, objectData = undefined }) => {
|
|||||||
}
|
}
|
||||||
}, [deployedSubJobsCount, subJobsCount, deployLoading, onOk])
|
}, [deployedSubJobsCount, subJobsCount, deployLoading, onOk])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (objectData?.printers) {
|
||||||
|
if (objectData.printers.every((printer) => printer.online)) {
|
||||||
|
setAllPrintersOnline(true)
|
||||||
|
} else {
|
||||||
|
setAllPrintersOnline(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setAllPrintersOnline(false)
|
||||||
|
}
|
||||||
|
}, [objectData])
|
||||||
|
|
||||||
|
console.log(objectData)
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: 'Confirm',
|
title: 'Confirm',
|
||||||
key: 'confirm',
|
key: 'confirm',
|
||||||
content: (
|
content: (
|
||||||
<Flex vertical gap={'middle'} style={{ width: '100%' }}>
|
<Flex vertical gap={'middle'} style={{ width: '100%' }}>
|
||||||
<Flex gap={'small'} align='center' style={{ width: '100%' }}>
|
<Flex gap={'middle'} align='center' style={{ width: '100%' }}>
|
||||||
<Text type='secondary'>Job:</Text>
|
<Descriptions
|
||||||
<ObjectSelect
|
column={1}
|
||||||
type={'job'}
|
items={[
|
||||||
style={{ flexGrow: 1 }}
|
{
|
||||||
value={objectData}
|
label: (
|
||||||
onChange={(newJob) => {
|
<Flex
|
||||||
setJob(newJob)
|
align='center'
|
||||||
}}
|
gap={'small'}
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
>
|
||||||
|
<Text type='secondary'>Job</Text>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
children: (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flexGrow: 1,
|
||||||
|
marginLeft: '3px',
|
||||||
|
marginBottom: '1px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
type={'object'}
|
||||||
|
name={'job'}
|
||||||
|
objectData={{ job: objectData }}
|
||||||
|
value={(object) => object.job}
|
||||||
|
readOnly={true}
|
||||||
|
isEditing={true}
|
||||||
|
objectType={'job'}
|
||||||
|
onChange={(newJob) => {
|
||||||
|
setJob(newJob)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<Flex
|
||||||
|
align='center'
|
||||||
|
gap={'small'}
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
>
|
||||||
|
<Text type='secondary'>Printers</Text>
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
children: (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flexGrow: 1,
|
||||||
|
marginLeft: '3px',
|
||||||
|
marginBottom: '1px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
type={'objectList'}
|
||||||
|
name={'printers'}
|
||||||
|
objectData={{ printers: objectData.printers }}
|
||||||
|
value={(object) => object.printers}
|
||||||
|
readOnly={true}
|
||||||
|
isEditing={true}
|
||||||
|
objectType={'printer'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@ -74,11 +150,11 @@ const DeployJob = ({ onOk, objectData = undefined }) => {
|
|||||||
scrollHeight={'200px'}
|
scrollHeight={'200px'}
|
||||||
visibleColumns={{
|
visibleColumns={{
|
||||||
printer: false,
|
printer: false,
|
||||||
'job._id': false,
|
job: false
|
||||||
'printer._id': false
|
|
||||||
}}
|
}}
|
||||||
masterFilter={{ 'job._id': job?._id }}
|
masterFilter={{ 'job._id': job?._id }}
|
||||||
size={'small'}
|
size={'small'}
|
||||||
|
showActions={!slicerIntegration}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
@ -89,9 +165,13 @@ const DeployJob = ({ onOk, objectData = undefined }) => {
|
|||||||
<WizardView
|
<WizardView
|
||||||
steps={steps}
|
steps={steps}
|
||||||
loading={deployLoading}
|
loading={deployLoading}
|
||||||
formValid={objectData != undefined}
|
formValid={objectData != undefined && allPrintersOnline}
|
||||||
title='Deploy Job'
|
title='Deploy Job'
|
||||||
showSteps={false}
|
showSteps={false}
|
||||||
|
showSkipButton={slicerIntegration}
|
||||||
|
onSkip={() => {
|
||||||
|
onOk()
|
||||||
|
}}
|
||||||
submitText='Deploy'
|
submitText='Deploy'
|
||||||
progress={(deployedSubJobsCount / subJobsCount) * 100}
|
progress={(deployedSubJobsCount / subJobsCount) * 100}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
@ -104,7 +184,8 @@ const DeployJob = ({ onOk, objectData = undefined }) => {
|
|||||||
DeployJob.propTypes = {
|
DeployJob.propTypes = {
|
||||||
onOk: PropTypes.func.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
objectData: PropTypes.object,
|
objectData: PropTypes.object,
|
||||||
reset: PropTypes.bool
|
reset: PropTypes.bool,
|
||||||
|
slicerIntegration: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DeployJob
|
export default DeployJob
|
||||||
|
|||||||
@ -1,17 +1,26 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import NewObjectForm from '../../common/NewObjectForm'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import WizardView from '../../common/WizardView'
|
import WizardView from '../../common/WizardView'
|
||||||
|
|
||||||
const NewJob = ({ onOk, defaultValues }) => {
|
const NewJob = ({
|
||||||
|
onOk,
|
||||||
|
defaultValues,
|
||||||
|
slicerIntegration = false,
|
||||||
|
currentStep,
|
||||||
|
onStepChange
|
||||||
|
}) => {
|
||||||
|
const initialValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
state: { type: 'draft' },
|
||||||
|
...defaultValues
|
||||||
|
}),
|
||||||
|
[defaultValues]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm type={'job'} defaultValues={initialValues}>
|
||||||
type={'job'}
|
|
||||||
defaultValues={{
|
|
||||||
state: { type: 'draft' },
|
|
||||||
...defaultValues
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
@ -59,10 +68,17 @@ const NewJob = ({ onOk, defaultValues }) => {
|
|||||||
loading={submitLoading}
|
loading={submitLoading}
|
||||||
formValid={formValid}
|
formValid={formValid}
|
||||||
title='New Job'
|
title='New Job'
|
||||||
|
showSteps={!slicerIntegration}
|
||||||
|
currentStep={currentStep}
|
||||||
|
onStepChange={onStepChange}
|
||||||
|
showSkipButton={slicerIntegration}
|
||||||
|
onSkip={() => {
|
||||||
|
onOk(null)
|
||||||
|
}}
|
||||||
onSubmit={async () => {
|
onSubmit={async () => {
|
||||||
const result = await handleSubmit()
|
const result = await handleSubmit()
|
||||||
if (result) {
|
if (result) {
|
||||||
onOk()
|
onOk(result)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -75,7 +91,10 @@ const NewJob = ({ onOk, defaultValues }) => {
|
|||||||
NewJob.propTypes = {
|
NewJob.propTypes = {
|
||||||
onOk: PropTypes.func.isRequired,
|
onOk: PropTypes.func.isRequired,
|
||||||
reset: PropTypes.bool,
|
reset: PropTypes.bool,
|
||||||
defaultValues: PropTypes.object
|
defaultValues: PropTypes.object,
|
||||||
|
slicerIntegration: PropTypes.bool,
|
||||||
|
currentStep: PropTypes.number,
|
||||||
|
onStepChange: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NewJob
|
export default NewJob
|
||||||
|
|||||||
@ -9,9 +9,9 @@ import InfoCollapse from '../../common/InfoCollapse.jsx'
|
|||||||
import ViewButton from '../../common/ViewButton.jsx'
|
import ViewButton from '../../common/ViewButton.jsx'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||||
import EditButtons from '../../common/EditButtons.jsx'
|
|
||||||
import ActionHandler from '../../common/ActionHandler.jsx'
|
import ActionHandler from '../../common/ActionHandler.jsx'
|
||||||
import ObjectActions from '../../common/ObjectActions.jsx'
|
import ObjectActions from '../../common/ObjectActions.jsx'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||||
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
||||||
@ -35,7 +35,7 @@ import ScrollBox from '../../common/ScrollBox.jsx'
|
|||||||
const log = loglevel.getLogger('ControlPrinter')
|
const log = loglevel.getLogger('ControlPrinter')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
|
|
||||||
const ControlPrinter = () => {
|
const ControlPrinter = ({ slicerIntegration = false }) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const objectFormRef = useRef(null)
|
const objectFormRef = useRef(null)
|
||||||
const actionHandlerRef = useRef(null)
|
const actionHandlerRef = useRef(null)
|
||||||
@ -226,7 +226,11 @@ const ControlPrinter = () => {
|
|||||||
type='printer'
|
type='printer'
|
||||||
id={printerId}
|
id={printerId}
|
||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
visibleActions={{ edit: false }}
|
visibleActions={{
|
||||||
|
edit: false,
|
||||||
|
info: !slicerIntegration,
|
||||||
|
control: !slicerIntegration
|
||||||
|
}}
|
||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
/>
|
/>
|
||||||
<ViewButton
|
<ViewButton
|
||||||
@ -275,24 +279,6 @@ const ControlPrinter = () => {
|
|||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
<Space>
|
|
||||||
<EditButtons
|
|
||||||
isEditing={objectFormState.isEditing}
|
|
||||||
handleUpdate={() => {
|
|
||||||
actionHandlerRef.current.callAction('finishEdit')
|
|
||||||
}}
|
|
||||||
cancelEditing={() => {
|
|
||||||
actionHandlerRef.current.callAction('cancelEdit')
|
|
||||||
}}
|
|
||||||
startEditing={() => {
|
|
||||||
actionHandlerRef.current.callAction('edit')
|
|
||||||
}}
|
|
||||||
editLoading={objectFormState.editLoading}
|
|
||||||
formValid={objectFormState.formValid}
|
|
||||||
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
|
||||||
loading={objectFormState.editLoading}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<ScrollBox>
|
<ScrollBox>
|
||||||
@ -353,11 +339,16 @@ const ControlPrinter = () => {
|
|||||||
currentSubJob: false,
|
currentSubJob: false,
|
||||||
'currentSubJob._id': false,
|
'currentSubJob._id': false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
updatedAt: false
|
updatedAt: false,
|
||||||
|
state: !slicerIntegration,
|
||||||
|
name: !slicerIntegration
|
||||||
|
}}
|
||||||
|
objectPropertyProps={{
|
||||||
|
showHyperlink: !slicerIntegration
|
||||||
}}
|
}}
|
||||||
objectData={printerObjectData}
|
objectData={printerObjectData}
|
||||||
type='printer'
|
type='printer'
|
||||||
labelWidth='100px'
|
labelWidth='120px'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@ -386,12 +377,15 @@ const ControlPrinter = () => {
|
|||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
loading={jobObjectLoading}
|
loading={jobObjectLoading}
|
||||||
column={sideBarVisible ? 1 : undefined}
|
column={sideBarVisible ? 1 : undefined}
|
||||||
showHyperlink={true}
|
showHyperlink={!slicerIntegration}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
printers: false,
|
printers: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
finishedAt: false
|
finishedAt: false
|
||||||
}}
|
}}
|
||||||
|
objectPropertyProps={{
|
||||||
|
showHyperlink: !slicerIntegration
|
||||||
|
}}
|
||||||
objectData={jobObjectData}
|
objectData={jobObjectData}
|
||||||
type='job'
|
type='job'
|
||||||
/>
|
/>
|
||||||
@ -428,12 +422,15 @@ const ControlPrinter = () => {
|
|||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
loading={subJobObjectLoading}
|
loading={subJobObjectLoading}
|
||||||
column={sideBarVisible ? 1 : undefined}
|
column={sideBarVisible ? 1 : undefined}
|
||||||
showHyperlink={true}
|
showHyperlink={!slicerIntegration}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
printers: false,
|
printers: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
finishedAt: false
|
finishedAt: false
|
||||||
}}
|
}}
|
||||||
|
objectPropertyProps={{
|
||||||
|
showHyperlink: !slicerIntegration
|
||||||
|
}}
|
||||||
objectData={subJobObjectData}
|
objectData={subJobObjectData}
|
||||||
type='subJob'
|
type='subJob'
|
||||||
/>
|
/>
|
||||||
@ -474,7 +471,7 @@ const ControlPrinter = () => {
|
|||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
loading={filamentStockObjectLoading}
|
loading={filamentStockObjectLoading}
|
||||||
column={sideBarVisible ? 1 : undefined}
|
column={sideBarVisible ? 1 : undefined}
|
||||||
showHyperlink={true}
|
showHyperlink={!slicerIntegration}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
createdAt: false
|
createdAt: false
|
||||||
@ -558,4 +555,8 @@ const ControlPrinter = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ControlPrinter.propTypes = {
|
||||||
|
slicerIntegration: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
export default ControlPrinter
|
export default ControlPrinter
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user