Add filament stock management to ControlPrinter component with modal integration for loading and unloading filament. Implement new actions for pausing, resuming, and canceling jobs, enhancing printer control functionality.

This commit is contained in:
Tom Butcher 2025-11-24 03:34:28 +00:00
parent afe25c1e09
commit 91e7121fd5

View File

@ -1,6 +1,6 @@
import { useState, useRef, useEffect, useContext } from 'react' import { useState, useRef, useEffect, useContext } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card, Splitter, Divider } from 'antd' import { Space, Flex, Card, Splitter, Divider, Modal } from 'antd'
import loglevel from 'loglevel' import loglevel from 'loglevel'
import config from '../../../../config.js' import config from '../../../../config.js'
import useCollapseState from '../../hooks/useCollapseState.js' import useCollapseState from '../../hooks/useCollapseState.js'
@ -28,6 +28,9 @@ import { useMediaQuery } from 'react-responsive'
import AlertsDisplay from '../../common/AlertsDisplay.jsx' import AlertsDisplay from '../../common/AlertsDisplay.jsx'
import { ApiServerContext } from '../../context/ApiServerContext.jsx' import { ApiServerContext } from '../../context/ApiServerContext.jsx'
import LoadFilamentStock from '../../Inventory/FilamentStocks/LoadFilamentStock.jsx'
import UnloadFilamentStock from '../../Inventory/FilamentStocks/UnloadFilamentStock.jsx'
const log = loglevel.getLogger('ControlPrinter') const log = loglevel.getLogger('ControlPrinter')
log.setLevel(config.logLevel) log.setLevel(config.logLevel)
@ -57,6 +60,9 @@ const ControlPrinter = () => {
collapseState.movement collapseState.movement
) )
const [loadFilamentStockOpen, setLoadFilamentStockOpen] = useState(false)
const [unloadFilamentStockOpen, setUnloadFilamentStockOpen] = useState(false)
useEffect(() => { useEffect(() => {
setSideBarVisible( setSideBarVisible(
collapseState.temperature || collapseState.temperature ||
@ -122,6 +128,39 @@ const ControlPrinter = () => {
}) })
} }
return true return true
},
pauseJob: () => {
if (connected == true) {
sendObjectAction(printerId, 'printer', {
type: 'pauseJob'
})
}
return true
},
resumeJob: () => {
if (connected == true) {
sendObjectAction(printerId, 'printer', {
type: 'resumeJob'
})
}
return true
},
cancelJob: () => {
if (connected == true) {
sendObjectAction(printerId, 'printer', {
type: 'cancelJob'
})
}
return true
},
loadFilamentStock: () => {
setLoadFilamentStockOpen(true)
return true
},
unloadFilamentStock: () => {
setUnloadFilamentStockOpen(true)
return true
} }
} }
@ -146,298 +185,338 @@ const ControlPrinter = () => {
) )
return ( return (
<Flex <>
gap='large' <Flex
vertical='true' gap='large'
style={{ vertical='true'
maxHeight: '100%', style={{
minHeight: 0 maxHeight: '100%',
}} minHeight: 0
> }}
<Flex justify={'space-between'}> >
<Space size='middle'> <Flex justify={'space-between'}>
<Space size='small'> <Space size='middle'>
<ObjectActions <Space size='small'>
type='printer' <ObjectActions
id={printerId} type='printer'
disabled={objectFormState.loading} id={printerId}
visibleActions={{ edit: false }} disabled={objectFormState.loading}
objectData={objectFormState.objectData} visibleActions={{ edit: false }}
/> objectData={objectFormState.objectData}
<ViewButton />
disabled={objectFormState.loading} <ViewButton
items={[ disabled={objectFormState.loading}
{ items={[
key: 'printer', {
label: 'Printer' key: 'printer',
}, label: 'Printer'
{ },
key: 'job', {
label: 'Job' key: 'job',
}, label: 'Job'
{ },
key: 'subJob', {
label: 'Sub Job' key: 'subJob',
}, label: 'Sub Job'
{ },
key: 'filamentStock', {
label: 'Filament Stock' key: 'filamentStock',
}, label: 'Filament Stock'
{ },
key: 'temperature', {
label: 'Temperature' key: 'temperature',
}, label: 'Temperature'
{ },
key: 'position', {
label: 'Position' key: 'position',
}, label: 'Position'
{ },
key: 'movement', {
label: 'Movement' key: 'movement',
}, label: 'Movement'
{ key: 'notes', label: 'Notes' } },
]} { key: 'notes', label: 'Notes' }
visibleState={collapseState} ]}
updateVisibleState={updateCollapseState} visibleState={collapseState}
/> updateVisibleState={updateCollapseState}
<AlertsDisplay alerts={objectFormState.objectData?.alerts} /> />
<AlertsDisplay alerts={objectFormState.objectData?.alerts} />
</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> </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>
<div style={{ height: '100%', overflowY: 'scroll' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<Flex vertical>
<Splitter className={'farmcontrol-splitter'}>
<Splitter.Panel>
<Flex vertical gap={'large'}>
<InfoCollapse
title={'Printer'}
icon={<PrinterIcon />}
collapseKey='printer'
active={collapseState.printer}
onToggle={(expanded) =>
updateCollapseState('printer', expanded)
}
>
<ObjectForm
id={printerId}
type='printer'
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({
loading: printerObjectLoading,
objectData: printerObjectData
}) => {
return (
<ObjectInfo
loading={printerObjectLoading}
column={sideBarVisible ? 1 : undefined}
visibleProperties={{
connectedAt: false,
vendor: false,
'vendor._id': false,
host: false,
'host._id': false,
'moonraker.port': false,
'moonraker.apiKey': false,
'moonraker.protocol': false,
'moonraker.host': false,
tags: false,
firmware: false,
alerts: false
}}
objectData={printerObjectData}
type='printer'
/>
)
}}
</ObjectForm>
</InfoCollapse>
<InfoCollapse
title={'Job'}
icon={<JobIcon />}
collapseKey='job'
active={collapseState.job}
onToggle={(expanded) =>
updateCollapseState('job', expanded)
}
>
{objectFormState.objectData?.currentJob?._id ? (
<ObjectForm
id={objectFormState.objectData.currentJob._id}
type='job'
onStateChange={() => {}}
>
{({
loading: jobObjectLoading,
objectData: jobObjectData
}) => {
return (
<ObjectInfo
loading={jobObjectLoading}
column={sideBarVisible ? 1 : undefined}
visibleProperties={{
printers: false,
createdAt: false
}}
objectData={jobObjectData}
type='job'
/>
)
}}
</ObjectForm>
) : (
<MissingPlaceholder
message={'No job.'}
hasBackground={false}
/>
)}
</InfoCollapse>
<InfoCollapse
title={'Sub Job'}
icon={<SubJobIcon />}
collapseKey='subJob'
active={collapseState.subJob}
onToggle={(expanded) =>
updateCollapseState('subJob', expanded)
}
>
{objectFormState.objectData?.currentSubJob?._id ? (
<ObjectForm
id={objectFormState.objectData.currentSubJob._id}
type='subjob'
onStateChange={() => {}}
>
{({
loading: subJobObjectLoading,
objectData: subJobObjectData
}) => {
return (
<ObjectInfo
loading={subJobObjectLoading}
column={sideBarVisible ? 1 : undefined}
visibleProperties={{
printers: false,
createdAt: false
}}
objectData={subJobObjectData}
type='subJob'
/>
)
}}
</ObjectForm>
) : (
<MissingPlaceholder
message={'No sub job.'}
hasBackground={false}
/>
)}
</InfoCollapse>
<InfoCollapse
title={'Filament Stock'}
icon={<FilamentStockIcon />}
collapseKey='filamentStock'
active={collapseState.filamentStock}
onToggle={(expanded) =>
updateCollapseState('filamentStock', expanded)
}
>
{objectFormState.objectData?.currentFilamentStock?._id ? (
<ObjectForm
id={
objectFormState.objectData.currentFilamentStock._id
}
type='filamentStock'
onStateChange={() => {}}
>
{({
loading: filamentStockObjectLoading,
objectData: filamentStockObjectData
}) => {
return (
<ObjectInfo
loading={filamentStockObjectLoading}
column={sideBarVisible ? 1 : undefined}
showHyperlink={true}
visibleProperties={{
updatedAt: false,
createdAt: false
}}
objectData={filamentStockObjectData}
type='filamentStock'
/>
)
}}
</ObjectForm>
) : (
<MissingPlaceholder
message={'No filament stock.'}
hasBackground={false}
/>
)}
</InfoCollapse>
</Flex>
</Splitter.Panel>
{sideBarVisible && !isMobile ? (
<Splitter.Panel
style={{ minWidth: '325px' }}
defaultSize='20%'
max='35%'
>
{sideBarItems}
</Splitter.Panel>
) : null}
</Splitter>
{isMobile ? (
<>
<Divider />
{sideBarItems}
</>
) : null}
</Flex>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={printerId} type='printer' />
</Card>
</InfoCollapse>
</Flex> </Flex>
</div>
</Flex> <div style={{ height: '100%', overflowY: 'scroll' }}>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<Flex vertical>
<Splitter className={'farmcontrol-splitter'}>
<Splitter.Panel>
<Flex vertical gap={'large'}>
<InfoCollapse
title={'Printer'}
icon={<PrinterIcon />}
collapseKey='printer'
active={collapseState.printer}
onToggle={(expanded) =>
updateCollapseState('printer', expanded)
}
>
<ObjectForm
id={printerId}
type='printer'
ref={objectFormRef}
onStateChange={(state) => {
console.log('Got edit form state change', state)
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({
loading: printerObjectLoading,
objectData: printerObjectData
}) => {
return (
<ObjectInfo
loading={printerObjectLoading}
column={sideBarVisible ? 1 : undefined}
visibleProperties={{
connectedAt: false,
vendor: false,
'vendor._id': false,
host: false,
'host._id': false,
'moonraker.port': false,
'moonraker.apiKey': false,
'moonraker.protocol': false,
'moonraker.host': false,
tags: false,
firmware: false,
alerts: false,
online: false,
active: false,
currentFilamentStock: false,
'currentFilamentStock._id': false,
currentJob: false,
'currentJob._id': false,
currentSubJob: false,
'currentSubJob._id': false
}}
objectData={printerObjectData}
type='printer'
/>
)
}}
</ObjectForm>
</InfoCollapse>
<InfoCollapse
title={'Job'}
icon={<JobIcon />}
collapseKey='job'
active={collapseState.job}
onToggle={(expanded) =>
updateCollapseState('job', expanded)
}
>
{objectFormState.objectData?.currentJob?._id ? (
<ObjectForm
id={objectFormState.objectData.currentJob._id}
type='job'
onStateChange={() => {}}
>
{({
loading: jobObjectLoading,
objectData: jobObjectData
}) => {
return (
<ObjectInfo
loading={jobObjectLoading}
column={sideBarVisible ? 1 : undefined}
visibleProperties={{
printers: false,
createdAt: false
}}
objectData={jobObjectData}
type='job'
/>
)
}}
</ObjectForm>
) : (
<MissingPlaceholder
message={'No job.'}
hasBackground={false}
/>
)}
</InfoCollapse>
<InfoCollapse
title={'Sub Job'}
icon={<SubJobIcon />}
collapseKey='subJob'
active={collapseState.subJob}
onToggle={(expanded) =>
updateCollapseState('subJob', expanded)
}
>
{objectFormState.objectData?.currentSubJob?._id ? (
<ObjectForm
id={objectFormState.objectData.currentSubJob._id}
type='subJob'
onStateChange={() => {}}
>
{({
loading: subJobObjectLoading,
objectData: subJobObjectData
}) => {
return (
<ObjectInfo
loading={subJobObjectLoading}
column={sideBarVisible ? 1 : undefined}
visibleProperties={{
printers: false,
createdAt: false
}}
objectData={subJobObjectData}
type='subJob'
/>
)
}}
</ObjectForm>
) : (
<MissingPlaceholder
message={'No sub job.'}
hasBackground={false}
/>
)}
</InfoCollapse>
<InfoCollapse
title={'Filament Stock'}
icon={<FilamentStockIcon />}
collapseKey='filamentStock'
active={collapseState.filamentStock}
onToggle={(expanded) =>
updateCollapseState('filamentStock', expanded)
}
>
{objectFormState.objectData?.currentFilamentStock
?._id ? (
<ObjectForm
id={
objectFormState.objectData.currentFilamentStock
._id
}
type='filamentStock'
onStateChange={() => {}}
>
{({
loading: filamentStockObjectLoading,
objectData: filamentStockObjectData
}) => {
return (
<ObjectInfo
loading={filamentStockObjectLoading}
column={sideBarVisible ? 1 : undefined}
showHyperlink={true}
visibleProperties={{
updatedAt: false,
createdAt: false
}}
objectData={filamentStockObjectData}
type='filamentStock'
/>
)
}}
</ObjectForm>
) : (
<MissingPlaceholder
message={'No filament stock.'}
hasBackground={false}
/>
)}
</InfoCollapse>
</Flex>
</Splitter.Panel>
{sideBarVisible && !isMobile ? (
<Splitter.Panel
style={{ minWidth: '325px' }}
defaultSize='20%'
max='35%'
>
{sideBarItems}
</Splitter.Panel>
) : null}
</Splitter>
{isMobile ? (
<>
<Divider />
{sideBarItems}
</>
) : null}
</Flex>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Card>
<NotesPanel _id={printerId} type='printer' />
</Card>
</InfoCollapse>
</Flex>
</div>
</Flex>
<Modal
open={loadFilamentStockOpen}
onCancel={() => setLoadFilamentStockOpen(false)}
footer={null}
width='700px'
destroyOnHidden={true}
>
<LoadFilamentStock
printer={objectFormState.objectData}
onOk={() => setLoadFilamentStockOpen(false)}
reset={false}
filamentStockLoaded={false}
/>
</Modal>
<Modal
open={unloadFilamentStockOpen}
onCancel={() => setUnloadFilamentStockOpen(false)}
footer={null}
width='700px'
destroyOnHidden={true}
>
<UnloadFilamentStock
printer={objectFormState.objectData}
onOk={() => setUnloadFilamentStockOpen(false)}
reset={false}
filamentStockLoaded={false}
/>
</Modal>
</>
) )
} }