diff --git a/src/App.jsx b/src/App.jsx
index f25235c..cbc32b3 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -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={}
/>
+ }
+ />
{
+ 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 (
+
+ }
+ onClick={showPendingSlicerUpload}
+ />
+
+ )
+ }
+ return null
+ }
+
+ return (
+
+
+
+
+
+ setEditFormState((previousState) => ({
+ ...previousState,
+ ...state
+ }))
+ }
+ >
+ {({ loading, objectData }) => (
+
+
+ {loading ? (
+
+ ) : (
+
+ )}
+
+ {loading
+ ? 'Loading...'
+ : objectData?.name || 'Slicer Integration'}
+
+ {!loading && (
+
+ )}
+
+ {slicerUploadsButton()}
+
+ )}
+
+
+
+
+
+ setShowSlicerUploadFlow(false)}
+ afterClose={handleSlicerUploadFlowClosed}
+ footer={null}
+ width={740}
+ destroyOnHidden={true}
+ >
+ {currentPendingSlicerUpload && (
+
+ )}
+
+
+
+ )
+}
+
+export default SlicerIntegration
diff --git a/src/components/Dashboard/Production/Printers/SlicerUploadFlow.jsx b/src/components/Dashboard/Production/Printers/SlicerUploadFlow.jsx
new file mode 100644
index 0000000..e77a0d5
--- /dev/null
+++ b/src/components/Dashboard/Production/Printers/SlicerUploadFlow.jsx
@@ -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: (
+ {
+ 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 ? (
+ {
+ if (job == null || job == undefined) {
+ onComplete({ gcodeFile: createdGCodeFile })
+ } else {
+ setCurrentSubStep(0)
+ setCreatedJob(job)
+ onJobCreated(job)
+ }
+ }}
+ />
+ ) : null
+ })
+
+ steps.push({
+ title: 'Deploy',
+ key: 'deploy',
+ content: createdJob ? (
+
+ onComplete({ gcodeFile: createdGCodeFile, job: createdJob })
+ }
+ />
+ ) : null
+ })
+ }
+
+ return (
+ {}}
+ />
+ )
+}
+
+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
diff --git a/src/routes/ProductionRoutes.jsx b/src/routes/ProductionRoutes.jsx
index d1b1f22..68d366e 100644
--- a/src/routes/ProductionRoutes.jsx
+++ b/src/routes/ProductionRoutes.jsx
@@ -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={}
/>,
+ }
+ />,
+ }
+ />,
+ }
+ />,
+ }
+ />,
} />,
} />,