Refactor Load and Unload Filament Stock components to utilize WizardView for improved user experience, integrate API server context for real-time temperature and filament sensor updates, and streamline form validation logic.

This commit is contained in:
Tom Butcher 2025-11-23 13:26:46 +00:00
parent 657d6a5c6e
commit ecf73c13c0
2 changed files with 163 additions and 273 deletions

View File

@ -1,27 +1,14 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react'
import { import { Form, Flex, Descriptions, Alert } from 'antd'
Form,
Button,
Typography,
Flex,
Steps,
Divider,
Descriptions,
Alert
} from 'antd'
import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { PrintServerContext } from '../../context/PrintServerContext'
import FilamentStockSelect from '../../common/FilamentStockSelect'
import PrinterSelect from '../../common/PrinterSelect'
import FilamentStockDisplay from '../../common/FilamentStockDisplay'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import PrinterState from '../../common/StateDisplay' import ObjectSelect from '../../common/ObjectSelect'
import ObjectDisplay from '../../common/ObjectDisplay'
const { Title } = Typography import WizardView from '../../common/WizardView'
import { ApiServerContext } from '../../context/ApiServerContext'
const LoadFilamentStock = ({ const LoadFilamentStock = ({
onOk, onOk,
@ -29,7 +16,8 @@ const LoadFilamentStock = ({
printer = null, printer = null,
filamentStockLoaded = false filamentStockLoaded = false
}) => { }) => {
const isMobile = useMediaQuery({ maxWidth: 768 }) const { connected, subscribeToObjectEvent, sendObjectAction } =
useContext(ApiServerContext)
LoadFilamentStock.propTypes = { LoadFilamentStock.propTypes = {
onOk: PropTypes.func.isRequired, onOk: PropTypes.func.isRequired,
@ -38,8 +26,6 @@ const LoadFilamentStock = ({
filamentStockLoaded: PropTypes.bool filamentStockLoaded: PropTypes.bool
} }
const { printServer } = useContext(PrintServerContext)
const initialLoadFilamentStockForm = { const initialLoadFilamentStockForm = {
printer: printer, printer: printer,
filamentStock: null filamentStock: null
@ -47,8 +33,7 @@ const LoadFilamentStock = ({
const [loadFilamentStockLoading, setLoadFilamentStockLoading] = const [loadFilamentStockLoading, setLoadFilamentStockLoading] =
useState(false) useState(false)
const [currentStep, setCurrentStep] = useState(0) const [formValid, setFormValid] = useState(false)
const [nextEnabled, setNextEnabled] = useState(false)
const [currentTemperature, setCurrentTemperature] = useState(-1) const [currentTemperature, setCurrentTemperature] = useState(-1)
const [targetTemperature, setTargetTemperature] = useState(0) const [targetTemperature, setTargetTemperature] = useState(0)
const [filamentSensorDetected, setFilamentSensorDetected] = const [filamentSensorDetected, setFilamentSensorDetected] =
@ -62,77 +47,69 @@ const LoadFilamentStock = ({
loadFilamentStockForm loadFilamentStockForm
) )
// Add websocket temperature monitoring
useEffect(() => { useEffect(() => {
if (loadFilamentStockFormValues.printer) { if (printer?._id && connected) {
const params = { const temperatureEventUnsubscribe = subscribeToObjectEvent(
printerId: loadFilamentStockFormValues.printer._id, printer._id,
objects: { 'printer',
extruder: null, 'temperature',
'filament_switch_sensor fsensor': null (event) => {
if (event.data?.extruder?.current) {
setCurrentTemperature(event.data.extruder.current)
}
if (event.data?.extruder?.target) {
setTargetTemperature(event.data.extruder.target)
} }
} }
const notifyStatusUpdate = (statusUpdate) => {
if (statusUpdate?.extruder?.temperature !== undefined) {
setCurrentTemperature(statusUpdate.extruder.temperature)
}
if (statusUpdate?.extruder?.target !== undefined) {
setTargetTemperature(statusUpdate.extruder.target)
}
if (
statusUpdate?.['filament_switch_sensor fsensor']
?.filament_detected !== undefined
) {
setFilamentSensorDetected(
Boolean(
statusUpdate['filament_switch_sensor fsensor'].filament_detected
) )
const filamentStockEventUnsubscribe = subscribeToObjectEvent(
printer._id,
'printer',
'filamentSensor',
(event) => {
console.log('filamentSensor', event.data)
setFilamentSensorDetected(event.data.detected)
}
) )
}
}
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
printServer.on('notify_status_update', notifyStatusUpdate)
return () => { return () => {
printServer.off('notify_status_update', notifyStatusUpdate) if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
printServer.emit('printer.objects.unsubscribe', params) if (filamentStockEventUnsubscribe) filamentStockEventUnsubscribe()
} }
} }
}, [printServer, loadFilamentStockFormValues.printer]) }, [printer?._id, connected])
useEffect(() => { useEffect(() => {
// Validate form fields
loadFilamentStockForm loadFilamentStockForm
.validateFields({ .validateFields({
validateOnly: true validateOnly: true
}) })
.then(() => setNextEnabled(filamentSensorDetected)) .then(() => {
.catch(() => setNextEnabled(false)) const hasPrinter = Boolean(loadFilamentStockFormValues.printer)
const hasFilamentStock = Boolean(
loadFilamentStockFormValues.filamentStock
)
// Step 0 (Preheat): needs printer + filament detected + (temp reached or no temp set)
const preheatReady =
hasPrinter &&
filamentSensorDetected &&
(targetTemperature === 0 || currentTemperature >= targetTemperature)
// Step 1+ (Required/Summary): needs filamentStock selected
// Form is valid if preheat is ready (for step 0) OR filamentStock is selected (for step 1+)
// This allows progression: step 0 can proceed when preheat is ready,
// and step 1+ can proceed when filamentStock is selected
setFormValid(preheatReady || (hasPrinter && hasFilamentStock))
})
.catch(() => setFormValid(false))
}, [ }, [
loadFilamentStockForm, loadFilamentStockForm,
loadFilamentStockFormUpdateValues, loadFilamentStockFormUpdateValues,
filamentSensorDetected loadFilamentStockFormValues,
])
useEffect(() => {
if (
filamentSensorDetected == true &&
currentTemperature >= targetTemperature
) {
setNextEnabled(filamentSensorDetected)
if (currentStep == 0) {
setCurrentStep(1)
}
} else if (filamentSensorDetected == false) {
setCurrentStep(0)
}
}, [
filamentSensorDetected, filamentSensorDetected,
targetTemperature,
currentTemperature, currentTemperature,
currentStep targetTemperature
]) ])
const summaryItems = [ const summaryItems = [
@ -140,8 +117,9 @@ const LoadFilamentStock = ({
key: 'filamentStock', key: 'filamentStock',
label: 'Stock', label: 'Stock',
children: loadFilamentStockFormValues.filamentStock ? ( children: loadFilamentStockFormValues.filamentStock ? (
<FilamentStockDisplay <ObjectDisplay
filamentStock={loadFilamentStockFormValues.filamentStock} objectType='filamentStock'
object={loadFilamentStockFormValues.filamentStock}
/> />
) : ( ) : (
'n/a' 'n/a'
@ -151,9 +129,12 @@ const LoadFilamentStock = ({
key: 'printer', key: 'printer',
label: 'Printer', label: 'Printer',
children: loadFilamentStockFormValues.printer ? ( children: loadFilamentStockFormValues.printer ? (
<PrinterState printer={loadFilamentStockFormValues.printer} /> <ObjectDisplay
objectType='printer'
object={loadFilamentStockFormValues.printer}
/>
) : ( ) : (
'n/a>' 'n/a'
) )
} }
] ]
@ -169,10 +150,16 @@ const LoadFilamentStock = ({
try { try {
// Set the extruder temperature // Set the extruder temperature
await printServer.emit('printer.filamentstock.load', { await sendObjectAction(
printerId: loadFilamentStockFormValues.printer._id, loadFilamentStockFormValues.printer._id,
filamentStockId: loadFilamentStockFormValues.filamentStock._id 'printer',
}) {
type: 'loadFilamentStock',
data: {
filamentStock: loadFilamentStockFormValues.filamentStock
}
}
)
onOk() onOk()
} finally { } finally {
setLoadFilamentStockLoading(false) setLoadFilamentStockLoading(false)
@ -196,7 +183,7 @@ const LoadFilamentStock = ({
} }
]} ]}
> >
<PrinterSelect checkable={false} /> <ObjectSelect type='printer' checkable={false} />
</Form.Item> </Form.Item>
{targetTemperature == 0 ? ( {targetTemperature == 0 ? (
<Alert <Alert
@ -226,10 +213,9 @@ const LoadFilamentStock = ({
{loadFilamentStockFormValues.printer ? ( {loadFilamentStockFormValues.printer ? (
<PrinterTemperaturePanel <PrinterTemperaturePanel
showHeatedBed={false} showBed={false}
showMoreInfo={false} showMoreInfo={false}
printerId={loadFilamentStockFormValues.printer._id} id={loadFilamentStockFormValues.printer._id}
shouldUnsubscribe={false}
/> />
) : null} ) : null}
</Flex> </Flex>
@ -250,7 +236,7 @@ const LoadFilamentStock = ({
} }
]} ]}
> >
<FilamentStockSelect /> <ObjectSelect type='filamentStock' />
</Form.Item> </Form.Item>
</> </>
) )
@ -267,26 +253,8 @@ const LoadFilamentStock = ({
] ]
return ( 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 }}>
Load Filament Stock
</Title>
<Form <Form
name='basic' name='loadFilamentStock'
autoComplete='off' autoComplete='off'
form={loadFilamentStockForm} form={loadFilamentStockForm}
onFinish={handleLoadFilamentStock} onFinish={handleLoadFilamentStock}
@ -298,44 +266,15 @@ const LoadFilamentStock = ({
} }
initialValues={initialLoadFilamentStockForm} initialValues={initialLoadFilamentStockForm}
> >
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div> <WizardView
title='Load Filament Stock'
<Flex justify={'end'}> steps={steps}
<Button onSubmit={() => loadFilamentStockForm.submit()}
style={{ formValid={formValid}
margin: '0 8px'
}}
onClick={() => setCurrentStep(currentStep - 1)}
disabled={!(currentStep > 0)}
>
Previous
</Button>
{currentStep < steps.length - 1 && (
<Button
type='primary'
disabled={!nextEnabled}
onClick={() => {
setCurrentStep(currentStep + 1)
}}
>
Next
</Button>
)}
{currentStep === steps.length - 1 && (
<Button
type='primary'
loading={loadFilamentStockLoading} loading={loadFilamentStockLoading}
onClick={() => { submitText='Done'
loadFilamentStockForm.submit() />
}}
>
Done
</Button>
)}
</Flex>
</Form> </Form>
</Flex>
</Flex>
) )
} }

View File

@ -1,34 +1,30 @@
import { useState, useContext, useEffect } from 'react' import { useState, useContext, useEffect } from 'react'
import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd' import { Form, Flex, Alert } from 'antd'
import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { PrintServerContext } from '../../context/PrintServerContext'
import PrinterSelect from '../../common/PrinterSelect' import ObjectSelect from '../../common/ObjectSelect'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
import WizardView from '../../common/WizardView'
import { ApiServerContext } from '../../context/ApiServerContext'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
const { Title } = Typography
const UnloadFilamentStock = ({ onOk, reset, printer = null }) => { const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
const { connected, subscribeToObjectEvent, sendObjectAction } =
useContext(ApiServerContext)
UnloadFilamentStock.propTypes = { UnloadFilamentStock.propTypes = {
onOk: PropTypes.func.isRequired, onOk: PropTypes.func.isRequired,
reset: PropTypes.bool.isRequired, reset: PropTypes.bool.isRequired,
printer: PropTypes.object printer: PropTypes.object
} }
const { printServer } = useContext(PrintServerContext)
const isMobile = useMediaQuery({ maxWidth: 768 })
const initialUnloadFilamentStockForm = { const initialUnloadFilamentStockForm = {
printer: printer printer: printer
} }
const [unloadFilamentStockLoading, setUnloadFilamentStockLoading] = const [unloadFilamentStockLoading, setUnloadFilamentStockLoading] =
useState(false) useState(false)
const [currentStep, setCurrentStep] = useState(0) const [formValid, setFormValid] = useState(false)
const [nextEnabled, setNextEnabled] = useState(false)
const [currentTemperature, setCurrentTemperature] = useState(-1) const [currentTemperature, setCurrentTemperature] = useState(-1)
const [targetTemperature, setTargetTemperature] = useState(0) const [targetTemperature, setTargetTemperature] = useState(0)
const [filamentSensorDetected, setFilamentSensorDetected] = useState(true) const [filamentSensorDetected, setFilamentSensorDetected] = useState(true)
@ -36,46 +32,35 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
const [unloadFilamentStockFormValues, setUnloadFilamentStockFormValues] = const [unloadFilamentStockFormValues, setUnloadFilamentStockFormValues] =
useState(initialUnloadFilamentStockForm) useState(initialUnloadFilamentStockForm)
// Add websocket temperature monitoring
useEffect(() => { useEffect(() => {
if (unloadFilamentStockFormValues.printer) { if (printer?._id && connected) {
const params = { const temperatureEventUnsubscribe = subscribeToObjectEvent(
printerId: unloadFilamentStockFormValues.printer._id, printer._id,
objects: { 'printer',
extruder: null, 'temperature',
'filament_switch_sensor fsensor': null (event) => {
if (event.data?.extruder?.current) {
setCurrentTemperature(event.data.extruder.current)
}
if (event.data?.extruder?.target) {
setTargetTemperature(event.data.extruder.target)
} }
} }
const notifyStatusUpdate = (statusUpdate) => {
if (statusUpdate?.extruder?.temperature !== undefined) {
setCurrentTemperature(statusUpdate.extruder.temperature)
}
if (statusUpdate?.extruder?.target !== undefined) {
setTargetTemperature(statusUpdate.extruder.target)
}
if (
statusUpdate?.['filament_switch_sensor fsensor']
?.filament_detected !== undefined
) {
setFilamentSensorDetected(
Boolean(
statusUpdate['filament_switch_sensor fsensor'].filament_detected
) )
const filamentStockEventUnsubscribe = subscribeToObjectEvent(
printer._id,
'printer',
'filamentSensor',
(event) => {
setFilamentSensorDetected(event.data.detected)
}
) )
}
}
printServer.emit('printer.objects.subscribe', params)
printServer.emit('printer.objects.query', params)
printServer.on('notify_status_update', notifyStatusUpdate)
return () => { return () => {
printServer.off('notify_status_update', notifyStatusUpdate) if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
printServer.emit('printer.objects.unsubscribe', params) if (filamentStockEventUnsubscribe) filamentStockEventUnsubscribe()
} }
} }
}, [printServer, unloadFilamentStockFormValues.printer]) }, [printer?._id, connected])
useEffect(() => { useEffect(() => {
if (reset) { if (reset) {
@ -90,14 +75,14 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
}) })
.then(() => { .then(() => {
// Only enable next if we have a printer selected, we're not loading, and we've reached target temperature // Only enable next if we have a printer selected, we're not loading, and we've reached target temperature
setNextEnabled( setFormValid(
Boolean(unloadFilamentStockFormValues.printer) && Boolean(unloadFilamentStockFormValues.printer) &&
!unloadFilamentStockLoading && !unloadFilamentStockLoading &&
currentTemperature + 1 > targetTemperature && currentTemperature + 1 > targetTemperature &&
targetTemperature != 0 targetTemperature != 0
) )
}) })
.catch(() => setNextEnabled(false)) .catch(() => setFormValid(false))
}, [ }, [
unloadFilamentStockForm, unloadFilamentStockForm,
unloadFilamentStockFormValues, unloadFilamentStockFormValues,
@ -109,10 +94,13 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
const handleUnloadFilamentStock = async () => { const handleUnloadFilamentStock = async () => {
setUnloadFilamentStockLoading(true) setUnloadFilamentStockLoading(true)
// Send G-code to retract the filament // Send G-code to retract the filament
await printServer.emit('printer.gcode.script', { await sendObjectAction(
printerId: unloadFilamentStockFormValues.printer._id, unloadFilamentStockFormValues.printer._id,
script: `_CLIENT_LINEAR_MOVE E=-200 F=1000` 'printer',
}) {
type: 'unloadFilamentStock'
}
)
//setUnloadFilamentStockLoading(false) //setUnloadFilamentStockLoading(false)
} }
@ -140,7 +128,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
} }
]} ]}
> >
<PrinterSelect checkable={false} /> <ObjectSelect type='printer' checkable={false} />
</Form.Item> </Form.Item>
{unloadFilamentStockLoading == false ? ( {unloadFilamentStockLoading == false ? (
@ -182,11 +170,11 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
/> />
)} )}
{unloadFilamentStockFormValues.printer ? ( {unloadFilamentStockFormValues.printer?._id ? (
<PrinterTemperaturePanel <PrinterTemperaturePanel
showHeatedBed={false} showBed={false}
showMoreInfo={false} showMoreInfo={false}
printerId={unloadFilamentStockFormValues.printer._id} id={unloadFilamentStockFormValues.printer._id}
/> />
) : null} ) : null}
</Flex> </Flex>
@ -195,24 +183,6 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
] ]
return ( 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 }}>
Unload Filament Stock
</Title>
<Form <Form
name='unloadFilamentStock' name='unloadFilamentStock'
autoComplete='off' autoComplete='off'
@ -226,34 +196,15 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
} }
initialValues={initialUnloadFilamentStockForm} initialValues={initialUnloadFilamentStockForm}
> >
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div> <WizardView
title='Unload Filament Stock'
<Flex justify={'end'}> steps={steps}
<Button onSubmit={() => unloadFilamentStockForm.submit()}
style={{ formValid={formValid}
margin: '0 8px'
}}
onClick={() => setCurrentStep(currentStep - 1)}
disabled={!(currentStep > 0)}
>
Previous
</Button>
{currentStep === steps.length - 1 && (
<Button
type='primary'
loading={unloadFilamentStockLoading} loading={unloadFilamentStockLoading}
disabled={!nextEnabled} submitText={unloadFilamentStockLoading ? 'Unloading...' : 'Unload'}
onClick={() => { />
unloadFilamentStockForm.submit()
}}
>
{unloadFilamentStockLoading ? 'Unloading...' : 'Unload'}
</Button>
)}
</Flex>
</Form> </Form>
</Flex>
</Flex>
) )
} }