Add Slicer Integration feature with lazy loading and routing support
- Introduced SlicerIntegration component for managing slicer uploads and printer control. - Added SlicerUploadFlow component to handle the upload process and job creation. - Updated App.jsx to include a new route for the SlicerIntegration component. - Enhanced ProductionRoutes with new routes for PrinterProfiles and FilamentProfiles, improving navigation and organization of printer-related features.
This commit is contained in:
parent
f5dffd5512
commit
c0a6968045
@ -6,6 +6,7 @@ import {
|
||||
Navigate
|
||||
} from 'react-router-dom'
|
||||
import { App, ConfigProvider } from 'antd'
|
||||
import { lazy } from 'react'
|
||||
|
||||
import Dashboard from './components/Dashboard/Dashboard.jsx'
|
||||
import PrivateRoute from './components/PrivateRoute'
|
||||
@ -33,6 +34,10 @@ import AuthCallback from './components/App/AuthCallback.jsx'
|
||||
import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx'
|
||||
import MarketplaceAuthCallback from './components/Dashboard/Sales/Marketplaces/MarketplaceAuthCallback.jsx'
|
||||
import AuthLaunch from './components/App/AppLaunch.jsx'
|
||||
const SlicerIntegration = lazy(
|
||||
() =>
|
||||
import('./components/Dashboard/Production/Printers/SlicerIntegration.jsx')
|
||||
)
|
||||
|
||||
import {
|
||||
ProductionRoutes,
|
||||
@ -121,6 +126,10 @@ const AppContent = () => {
|
||||
path='/email/notification'
|
||||
element={<EmailNotificationTemplate />}
|
||||
/>
|
||||
<Route
|
||||
path='/slicer'
|
||||
element={<SlicerIntegration />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/dashboard'
|
||||
|
||||
@ -0,0 +1,225 @@
|
||||
import { useContext, useEffect, useState } from 'react'
|
||||
import { Divider, Flex, Layout, Typography, Button, Badge, Modal } from 'antd'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import ControlPrinter from './ControlPrinter.jsx'
|
||||
import SlicerUploadFlow from './SlicerUploadFlow.jsx'
|
||||
import PrinterIcon from '../../../Icons/PrinterIcon.jsx'
|
||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||
import FileIcon from '../../../Icons/FileIcon.jsx'
|
||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||
import { ApiServerContext } from '../../context/ApiServerContext.jsx'
|
||||
import { useThemeContext } from '../../context/ThemeContext.jsx'
|
||||
|
||||
const { Content } = Layout
|
||||
const { Title } = Typography
|
||||
|
||||
const SlicerIntegration = () => {
|
||||
const location = useLocation()
|
||||
const printerId = new URLSearchParams(location.search).get('printerId')
|
||||
const { updateObject } = useContext(ApiServerContext)
|
||||
|
||||
const [pendingSlicerUploads, setPendingSlicerUploads] = useState([])
|
||||
const [currentPendingSlicerUpload, setCurrentPendingSlicerUpload] =
|
||||
useState(null)
|
||||
|
||||
const { setPrimaryColorOverride } = useThemeContext()
|
||||
|
||||
useEffect(() => {
|
||||
setPrimaryColorOverride('#00baa8')
|
||||
}, [])
|
||||
|
||||
const [objectFormState, setEditFormState] = useState({
|
||||
isEditing: false,
|
||||
editLoading: false,
|
||||
formValid: false,
|
||||
locked: false,
|
||||
loading: false,
|
||||
objectData: {
|
||||
pendingSlicerUploads: []
|
||||
}
|
||||
})
|
||||
const [showSlicerUploadFlow, setShowSlicerUploadFlow] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setPendingSlicerUploads(
|
||||
objectFormState.objectData?.pendingSlicerUploads || []
|
||||
)
|
||||
}, [objectFormState.objectData?.pendingSlicerUploads])
|
||||
|
||||
useEffect(() => {
|
||||
const nextUpload = pendingSlicerUploads.find(
|
||||
(upload) => upload.new === true
|
||||
)
|
||||
|
||||
if (nextUpload) {
|
||||
setCurrentPendingSlicerUpload(nextUpload)
|
||||
setShowSlicerUploadFlow(true)
|
||||
} else {
|
||||
setCurrentPendingSlicerUpload(null)
|
||||
setShowSlicerUploadFlow(false)
|
||||
}
|
||||
}, [pendingSlicerUploads])
|
||||
|
||||
const showPendingSlicerUpload = () => {
|
||||
const upload =
|
||||
pendingSlicerUploads.find(
|
||||
(pendingUpload) => pendingUpload.new === true
|
||||
) || pendingSlicerUploads[0]
|
||||
|
||||
if (upload) {
|
||||
setCurrentPendingSlicerUpload(upload)
|
||||
setShowSlicerUploadFlow(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleGCodeFileCreated = async (gcodeFile) => {
|
||||
const updatedUploads = pendingSlicerUploads.map((upload) =>
|
||||
upload._id === currentPendingSlicerUpload?._id
|
||||
? { ...upload, gcodeFile: gcodeFile }
|
||||
: upload
|
||||
)
|
||||
|
||||
setPendingSlicerUploads(updatedUploads)
|
||||
setCurrentPendingSlicerUpload((previous) => ({
|
||||
...previous,
|
||||
gcodeFile
|
||||
}))
|
||||
await updateObject(printerId, 'printer', {
|
||||
...objectFormState.objectData,
|
||||
pendingSlicerUploads: updatedUploads
|
||||
})
|
||||
}
|
||||
|
||||
const handleJobCreated = async (job) => {
|
||||
const updatedUploads = pendingSlicerUploads.map((upload) =>
|
||||
upload._id === currentPendingSlicerUpload?._id
|
||||
? { ...upload, job }
|
||||
: upload
|
||||
)
|
||||
|
||||
setPendingSlicerUploads(updatedUploads)
|
||||
setCurrentPendingSlicerUpload((previous) => ({
|
||||
...previous,
|
||||
job
|
||||
}))
|
||||
await updateObject(printerId, 'printer', {
|
||||
...objectFormState.objectData,
|
||||
pendingSlicerUploads: updatedUploads
|
||||
})
|
||||
}
|
||||
|
||||
const handleSlicerUploadComplete = async () => {
|
||||
const updatedUploads = pendingSlicerUploads.filter(
|
||||
(upload) => upload._id !== currentPendingSlicerUpload?._id
|
||||
)
|
||||
|
||||
setPendingSlicerUploads(updatedUploads)
|
||||
await updateObject(printerId, 'printer', {
|
||||
...objectFormState.objectData,
|
||||
pendingSlicerUploads: updatedUploads
|
||||
})
|
||||
}
|
||||
|
||||
const handleSlicerUploadFlowClosed = async () => {
|
||||
const updatedUploads = pendingSlicerUploads.map((upload) =>
|
||||
upload._id === currentPendingSlicerUpload?._id
|
||||
? { ...upload, new: false }
|
||||
: upload
|
||||
)
|
||||
|
||||
setPendingSlicerUploads(updatedUploads)
|
||||
await updateObject(printerId, 'printer', {
|
||||
...objectFormState.objectData,
|
||||
pendingSlicerUploads: updatedUploads
|
||||
})
|
||||
}
|
||||
|
||||
const slicerUploadsButton = () => {
|
||||
if (pendingSlicerUploads.length > 0) {
|
||||
return (
|
||||
<Badge
|
||||
count={pendingSlicerUploads.length}
|
||||
style={{ marginTop: '8px' }}
|
||||
size='small'
|
||||
>
|
||||
<Button
|
||||
type='text'
|
||||
icon={<FileIcon />}
|
||||
onClick={showPendingSlicerUpload}
|
||||
/>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout style={{ height: 'var(--unit-100vh)' }}>
|
||||
<Layout style={{ padding: '24px' }}>
|
||||
<Content>
|
||||
<Flex vertical style={{ height: '100%' }} gap='20px'>
|
||||
<ObjectForm
|
||||
id={printerId}
|
||||
type='printer'
|
||||
onStateChange={(state) =>
|
||||
setEditFormState((previousState) => ({
|
||||
...previousState,
|
||||
...state
|
||||
}))
|
||||
}
|
||||
>
|
||||
{({ loading, objectData }) => (
|
||||
<Flex justify='space-between' align='center'>
|
||||
<Flex align='center' gap='middle'>
|
||||
{loading ? (
|
||||
<LoadingOutlined spin style={{ fontSize: '26px' }} />
|
||||
) : (
|
||||
<PrinterIcon style={{ fontSize: '26px' }} />
|
||||
)}
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
{loading
|
||||
? 'Loading...'
|
||||
: objectData?.name || 'Slicer Integration'}
|
||||
</Title>
|
||||
{!loading && (
|
||||
<ObjectProperty
|
||||
objectData={objectData}
|
||||
{...getModelProperty('printer', 'state')}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{slicerUploadsButton()}
|
||||
</Flex>
|
||||
)}
|
||||
</ObjectForm>
|
||||
<Divider style={{ margin: '0' }} />
|
||||
<ControlPrinter slicerIntegration={true} />
|
||||
</Flex>
|
||||
</Content>
|
||||
<Modal
|
||||
open={showSlicerUploadFlow}
|
||||
onCancel={() => setShowSlicerUploadFlow(false)}
|
||||
afterClose={handleSlicerUploadFlowClosed}
|
||||
footer={null}
|
||||
width={740}
|
||||
destroyOnHidden={true}
|
||||
>
|
||||
{currentPendingSlicerUpload && (
|
||||
<SlicerUploadFlow
|
||||
key={currentPendingSlicerUpload._id}
|
||||
currentPendingSlicerUpload={currentPendingSlicerUpload}
|
||||
printer={objectFormState.objectData}
|
||||
onGCodeFileCreated={handleGCodeFileCreated}
|
||||
onJobCreated={handleJobCreated}
|
||||
onComplete={handleSlicerUploadComplete}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default SlicerIntegration
|
||||
@ -0,0 +1,152 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import NewGCodeFile from '../GCodeFiles/NewGCodeFile.jsx'
|
||||
import NewJob from '../Jobs/NewJob.jsx'
|
||||
import DeployJob from '../Jobs/DeployJob.jsx'
|
||||
import WizardView from '../../common/WizardView.jsx'
|
||||
|
||||
const SlicerUploadFlow = ({
|
||||
currentPendingSlicerUpload,
|
||||
printer,
|
||||
onGCodeFileCreated,
|
||||
onJobCreated,
|
||||
onComplete
|
||||
}) => {
|
||||
const [createdGCodeFile, setCreatedGCodeFile] = useState(
|
||||
() => currentPendingSlicerUpload.gcodeFile || null
|
||||
)
|
||||
const [createdJob, setCreatedJob] = useState(
|
||||
() => currentPendingSlicerUpload.job || null
|
||||
)
|
||||
const [currentSubStep, setCurrentSubStep] = useState(0)
|
||||
|
||||
const gcodeFileDefaults = useMemo(
|
||||
() => ({
|
||||
...(currentPendingSlicerUpload?.properties || {}),
|
||||
file: currentPendingSlicerUpload?.file
|
||||
}),
|
||||
[currentPendingSlicerUpload]
|
||||
)
|
||||
|
||||
const jobDefaults = useMemo(
|
||||
() => ({
|
||||
quantity: 1,
|
||||
gcodeFile: createdGCodeFile,
|
||||
printers: [printer]
|
||||
}),
|
||||
[createdGCodeFile, printer]
|
||||
)
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: 'GCode File',
|
||||
key: 'gcodeFile',
|
||||
subSteps: [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required'
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary'
|
||||
}
|
||||
],
|
||||
content: (
|
||||
<NewGCodeFile
|
||||
defaultValues={gcodeFileDefaults}
|
||||
slicerIntegration={true}
|
||||
shouldPrint={currentPendingSlicerUpload.shouldPrint}
|
||||
currentStep={currentSubStep}
|
||||
onStepChange={setCurrentSubStep}
|
||||
onOk={(gcodeFile) => {
|
||||
if (currentPendingSlicerUpload.shouldPrint) {
|
||||
setCurrentSubStep(0)
|
||||
setCreatedGCodeFile(gcodeFile)
|
||||
onGCodeFileCreated(gcodeFile)
|
||||
} else {
|
||||
onComplete({ gcodeFile })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
if (currentPendingSlicerUpload.shouldPrint) {
|
||||
steps.push({
|
||||
title: 'Print Job',
|
||||
key: 'job',
|
||||
subSteps: [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required'
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary'
|
||||
}
|
||||
],
|
||||
content: createdGCodeFile ? (
|
||||
<NewJob
|
||||
slicerIntegration={true}
|
||||
defaultValues={jobDefaults}
|
||||
currentStep={currentSubStep}
|
||||
onStepChange={setCurrentSubStep}
|
||||
onOk={(job) => {
|
||||
if (job == null || job == undefined) {
|
||||
onComplete({ gcodeFile: createdGCodeFile })
|
||||
} else {
|
||||
setCurrentSubStep(0)
|
||||
setCreatedJob(job)
|
||||
onJobCreated(job)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
})
|
||||
|
||||
steps.push({
|
||||
title: 'Deploy',
|
||||
key: 'deploy',
|
||||
content: createdJob ? (
|
||||
<DeployJob
|
||||
objectData={createdJob}
|
||||
slicerIntegration={true}
|
||||
onOk={() =>
|
||||
onComplete({ gcodeFile: createdGCodeFile, job: createdJob })
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<WizardView
|
||||
title='Slicer Upload'
|
||||
steps={steps}
|
||||
currentStep={createdJob ? 2 : createdGCodeFile ? 1 : 0}
|
||||
currentSubStep={currentSubStep}
|
||||
showButtons={false}
|
||||
showTitle={false}
|
||||
showSteps={currentPendingSlicerUpload.shouldPrint}
|
||||
formValid={true}
|
||||
onSubmit={() => {}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
SlicerUploadFlow.propTypes = {
|
||||
currentPendingSlicerUpload: PropTypes.shape({
|
||||
file: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||
gcodeFile: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
job: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
shouldPrint: PropTypes.bool.isRequired,
|
||||
properties: PropTypes.object
|
||||
}).isRequired,
|
||||
printer: PropTypes.object.isRequired,
|
||||
onGCodeFileCreated: PropTypes.func.isRequired,
|
||||
onJobCreated: PropTypes.func.isRequired,
|
||||
onComplete: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default SlicerUploadFlow
|
||||
@ -13,6 +13,20 @@ const ControlPrinter = lazy(
|
||||
const PrinterInfo = lazy(
|
||||
() => import('../components/Dashboard/Production/Printers/PrinterInfo.jsx')
|
||||
)
|
||||
const PrinterProfiles = lazy(
|
||||
() => import('../components/Dashboard/Production/PrinterProfiles.jsx')
|
||||
)
|
||||
const PrinterProfileInfo = lazy(
|
||||
() =>
|
||||
import('../components/Dashboard/Production/PrinterProfiles/PrinterProfileInfo.jsx')
|
||||
)
|
||||
const FilamentProfiles = lazy(
|
||||
() => import('../components/Dashboard/Production/FilamentProfiles.jsx')
|
||||
)
|
||||
const FilamentProfileInfo = lazy(
|
||||
() =>
|
||||
import('../components/Dashboard/Production/FilamentProfiles/FilamentProfileInfo.jsx')
|
||||
)
|
||||
const Jobs = lazy(() => import('../components/Dashboard/Production/Jobs.jsx'))
|
||||
const JobInfo = lazy(
|
||||
() => import('../components/Dashboard/Production/Jobs/JobInfo.jsx')
|
||||
@ -52,6 +66,26 @@ const ProductionRoutes = [
|
||||
path='production/printers/info'
|
||||
element={<PrinterInfo />}
|
||||
/>,
|
||||
<Route
|
||||
key='printerprofiles'
|
||||
path='production/printerprofiles'
|
||||
element={<PrinterProfiles />}
|
||||
/>,
|
||||
<Route
|
||||
key='printerprofiles-info'
|
||||
path='production/printerprofiles/info'
|
||||
element={<PrinterProfileInfo />}
|
||||
/>,
|
||||
<Route
|
||||
key='filamentprofiles'
|
||||
path='production/filamentprofiles'
|
||||
element={<FilamentProfiles />}
|
||||
/>,
|
||||
<Route
|
||||
key='filamentprofiles-info'
|
||||
path='production/filamentprofiles/info'
|
||||
element={<FilamentProfileInfo />}
|
||||
/>,
|
||||
<Route key='jobs' path='production/jobs' element={<Jobs />} />,
|
||||
<Route key='subjobs' path='production/subjobs' element={<SubJobs />} />,
|
||||
<Route
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user