Refactor NewPrinter component to utilize NewObjectForm and WizardView for improved structure and user experience; streamline printer setup process and enhance form validation.
This commit is contained in:
parent
5300874b80
commit
6c0933b797
@ -1,564 +1,109 @@
|
||||
import { useState, useContext, useEffect, useCallback } from 'react'
|
||||
import axios from 'axios'
|
||||
import {
|
||||
Form,
|
||||
Button,
|
||||
message,
|
||||
Typography,
|
||||
Flex,
|
||||
Steps,
|
||||
Divider,
|
||||
Input,
|
||||
Select,
|
||||
Space,
|
||||
Descriptions,
|
||||
List,
|
||||
InputNumber,
|
||||
notification,
|
||||
Progress,
|
||||
Modal,
|
||||
Radio
|
||||
} from 'antd'
|
||||
import { SearchOutlined, SettingOutlined } from '@ant-design/icons'
|
||||
import PropTypes from 'prop-types'
|
||||
import { PrintServerContext } from '../../context/PrintServerContext'
|
||||
import EditIcon from '../../../Icons/EditIcon.jsx'
|
||||
import ObjectInfo from '../../common/ObjectInfo'
|
||||
import NewObjectForm from '../../common/NewObjectForm'
|
||||
import WizardView from '../../common/WizardView'
|
||||
|
||||
import config from '../../../../config.js'
|
||||
|
||||
const { Title } = Typography
|
||||
|
||||
const initialNewPrinterForm = {
|
||||
moonraker: {
|
||||
protocol: 'ws',
|
||||
host: '',
|
||||
port: '',
|
||||
apiKey: ''
|
||||
}
|
||||
const NewPrinter = ({ onOk }) => {
|
||||
return (
|
||||
<NewObjectForm
|
||||
type={'printer'}
|
||||
defaultValues={{
|
||||
moonraker: {
|
||||
port: 7125,
|
||||
protocol: 'ws'
|
||||
},
|
||||
active: true
|
||||
}}
|
||||
>
|
||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||
const steps = [
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='printer'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={true}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Optional',
|
||||
key: 'optional',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='printer'
|
||||
column={1}
|
||||
bordered={false}
|
||||
isEditing={true}
|
||||
required={false}
|
||||
objectData={objectData}
|
||||
visibleProperties={{
|
||||
firmware: false,
|
||||
currentFilamentStock: false,
|
||||
'currentFilamentStock._id': false,
|
||||
currentJob: false,
|
||||
'currentJob._id': false,
|
||||
currentSubJob: false,
|
||||
'currentSubJob._id': false,
|
||||
alerts: false
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<ObjectInfo
|
||||
type='printer'
|
||||
column={1}
|
||||
bordered={false}
|
||||
visibleProperties={{
|
||||
_id: false,
|
||||
createdAt: false,
|
||||
updatedAt: false,
|
||||
connectedAt: false,
|
||||
state: false,
|
||||
firmware: false,
|
||||
currentFilamentStock: false,
|
||||
'currentFilamentStock._id': false,
|
||||
currentJob: false,
|
||||
'currentJob._id': false,
|
||||
currentSubJob: false,
|
||||
'currentSubJob._id': false,
|
||||
alerts: false
|
||||
}}
|
||||
isEditing={false}
|
||||
objectData={objectData}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]
|
||||
return (
|
||||
<WizardView
|
||||
steps={steps}
|
||||
loading={submitLoading}
|
||||
formValid={formValid}
|
||||
title='New Printer'
|
||||
onSubmit={() => {
|
||||
handleSubmit()
|
||||
onOk()
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</NewObjectForm>
|
||||
)
|
||||
}
|
||||
|
||||
const NewPrinter = ({ onOk, reset }) => {
|
||||
NewPrinter.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
const { printServer } = useContext(PrintServerContext)
|
||||
const [messageApi, contextHolder] = message.useMessage()
|
||||
const [notificationApi, notificationContextHolder] =
|
||||
notification.useNotification()
|
||||
const [newPrinterLoading, setNewPrinterLoading] = useState(false)
|
||||
const [currentStep, setCurrentStep] = useState(0)
|
||||
const [nextEnabled, setNextEnabled] = useState(false)
|
||||
const [newPrinterForm] = Form.useForm()
|
||||
const [newPrinterFormValues, setNewPrinterFormValues] = useState(
|
||||
initialNewPrinterForm
|
||||
)
|
||||
const [discoveredPrinters, setDiscoveredPrinters] = useState([])
|
||||
const [discovering, setDiscovering] = useState(false)
|
||||
const [showManualSetup, setShowManualSetup] = useState(false)
|
||||
const [scanPort, setScanPort] = useState(7125)
|
||||
const [scanProtocol, setScanProtocol] = useState('ws')
|
||||
const [editingHostname, setEditingHostname] = useState(null)
|
||||
const [hostnameInput, setHostnameInput] = useState('')
|
||||
const [initialized, setInitialized] = useState(false)
|
||||
|
||||
const newPrinterFormUpdateValues = Form.useWatch([], newPrinterForm)
|
||||
|
||||
useEffect(() => {
|
||||
newPrinterForm
|
||||
.validateFields({
|
||||
validateOnly: true
|
||||
})
|
||||
.then(() => {
|
||||
if (currentStep === 0) {
|
||||
const moonraker = newPrinterForm.getFieldValue('moonraker')
|
||||
setNextEnabled(
|
||||
!!(moonraker?.protocol && moonraker?.host && moonraker?.port)
|
||||
)
|
||||
} else if (currentStep === 1) {
|
||||
const name = newPrinterForm.getFieldValue('name')
|
||||
setNextEnabled(!!name)
|
||||
} else {
|
||||
setNextEnabled(true)
|
||||
}
|
||||
})
|
||||
.catch(() => setNextEnabled(false))
|
||||
}, [newPrinterForm, newPrinterFormUpdateValues, currentStep])
|
||||
|
||||
const summaryItems = [
|
||||
{
|
||||
key: 'name',
|
||||
label: 'Name',
|
||||
children: newPrinterFormValues.name
|
||||
},
|
||||
{
|
||||
key: 'protocol',
|
||||
label: 'Protocol',
|
||||
children: newPrinterFormValues.moonraker?.protocol
|
||||
},
|
||||
{
|
||||
key: 'host',
|
||||
label: 'Host',
|
||||
children: newPrinterFormValues.moonraker?.host
|
||||
},
|
||||
{
|
||||
key: 'port',
|
||||
label: 'Port',
|
||||
children: newPrinterFormValues.moonraker?.port
|
||||
}
|
||||
]
|
||||
|
||||
useEffect(() => {
|
||||
if (reset) {
|
||||
newPrinterForm.resetFields()
|
||||
}
|
||||
}, [reset, newPrinterForm])
|
||||
|
||||
const handlePrinterSelect = (printer) => {
|
||||
newPrinterForm.setFieldsValue({
|
||||
moonraker: {
|
||||
protocol: printer.protocol,
|
||||
host: printer.host,
|
||||
port: printer.port
|
||||
}
|
||||
})
|
||||
setNewPrinterFormValues({
|
||||
...newPrinterFormValues,
|
||||
moonraker: {
|
||||
protocol: printer.protocol,
|
||||
host: printer.host,
|
||||
port: printer.port
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleHostnameEdit = (printer, newHostname) => {
|
||||
if (newHostname && newHostname.trim() !== '') {
|
||||
const updatedPrinter = {
|
||||
...printer,
|
||||
host: newHostname.trim()
|
||||
}
|
||||
setDiscoveredPrinters((prev) =>
|
||||
prev.map((p) => (p.host === printer.host ? updatedPrinter : p))
|
||||
)
|
||||
setEditingHostname(null)
|
||||
setHostnameInput('')
|
||||
}
|
||||
}
|
||||
|
||||
const showEditHostnameDialog = (printer) => {
|
||||
setEditingHostname(printer.host)
|
||||
setHostnameInput(printer.host)
|
||||
}
|
||||
|
||||
const handleNewPrinter = async () => {
|
||||
setNewPrinterLoading(true)
|
||||
try {
|
||||
await axios.post(
|
||||
`${config.backendUrl}/printers`,
|
||||
{
|
||||
...newPrinterFormValues
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
withCredentials: true
|
||||
}
|
||||
)
|
||||
|
||||
onOk()
|
||||
} catch (error) {
|
||||
messageApi.error('Error adding new printer: ' + error.message)
|
||||
} finally {
|
||||
setNewPrinterLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const notifyScanNetworkFound = useCallback(
|
||||
(data) => {
|
||||
const newPrinter = {
|
||||
protocol: scanProtocol,
|
||||
host: data.hostname || data.ip,
|
||||
port: scanPort
|
||||
}
|
||||
notificationApi.info({
|
||||
message: 'Printer Found',
|
||||
description: `Printer found: ${data.hostname || data.ip}!`
|
||||
})
|
||||
setDiscoveredPrinters((prev) => [...prev, newPrinter])
|
||||
},
|
||||
[scanProtocol, scanPort, notificationApi]
|
||||
)
|
||||
|
||||
const notifyScanNetworkComplete = useCallback(
|
||||
(data) => {
|
||||
setDiscovering(false)
|
||||
notificationApi.destroy('network-scan')
|
||||
if (data == false) {
|
||||
messageApi.error('Error discovering printers!')
|
||||
} else {
|
||||
messageApi.success('Finished discovering printers!')
|
||||
}
|
||||
},
|
||||
[messageApi, notificationApi]
|
||||
)
|
||||
|
||||
const notifyScanNetworkProgress = useCallback(
|
||||
(data) => {
|
||||
notificationApi.info({
|
||||
message: 'Scanning Network',
|
||||
description: (
|
||||
<div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
Scanning IP: {data.currentIP}
|
||||
</div>
|
||||
<Progress
|
||||
percent={data.progress}
|
||||
size='small'
|
||||
status='active'
|
||||
showInfo={false}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
duration: 0,
|
||||
key: 'network-scan',
|
||||
icon: null,
|
||||
placement: 'bottomRight',
|
||||
style: {
|
||||
width: 360
|
||||
},
|
||||
className: 'network-scan-notification',
|
||||
closeIcon: null,
|
||||
onClose: () => {},
|
||||
btn: null
|
||||
})
|
||||
},
|
||||
[notificationApi]
|
||||
)
|
||||
|
||||
const discoverPrinters = useCallback(() => {
|
||||
if (!discovering) {
|
||||
setDiscovering(true)
|
||||
setDiscoveredPrinters([])
|
||||
messageApi.info('Discovering printers...')
|
||||
printServer.off('notify_scan_network_found')
|
||||
printServer.off('notify_scan_network_progress')
|
||||
printServer.off('notify_scan_network_complete')
|
||||
|
||||
printServer.on('notify_scan_network_found', notifyScanNetworkFound)
|
||||
printServer.on('notify_scan_network_progress', notifyScanNetworkProgress)
|
||||
printServer.on('notify_scan_network_complete', notifyScanNetworkComplete)
|
||||
|
||||
printServer.emit('bridge.scan_network.start', {
|
||||
port: scanPort,
|
||||
protocol: scanProtocol
|
||||
})
|
||||
}
|
||||
}, [
|
||||
discovering,
|
||||
printServer,
|
||||
scanPort,
|
||||
scanProtocol,
|
||||
messageApi,
|
||||
notifyScanNetworkFound,
|
||||
notifyScanNetworkProgress,
|
||||
notifyScanNetworkComplete
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
setInitialized(true)
|
||||
if (!initialized) {
|
||||
discoverPrinters()
|
||||
}
|
||||
}, [initialized, discoverPrinters])
|
||||
|
||||
const stopDiscovery = () => {
|
||||
if (discovering) {
|
||||
setDiscovering(false)
|
||||
notificationApi.destroy('network-scan')
|
||||
messageApi.info('Stopping discovery...')
|
||||
printServer.off('notify_scan_network_found')
|
||||
printServer.off('notify_scan_network_progress')
|
||||
printServer.off('notify_scan_network_complete')
|
||||
printServer.emit('bridge.scan_network.stop', (response) => {
|
||||
if (response == false) {
|
||||
messageApi.error('Error stopping discovery!')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handlePortChange = (value) => {
|
||||
stopDiscovery()
|
||||
setScanPort(value)
|
||||
}
|
||||
|
||||
const handleProtocolChange = (value) => {
|
||||
stopDiscovery()
|
||||
setScanProtocol(value)
|
||||
}
|
||||
|
||||
const steps = [
|
||||
{
|
||||
title: 'Discovery',
|
||||
key: 'discovery',
|
||||
content: (
|
||||
<>
|
||||
<Flex vertical style={{ width: '100%' }} gap='large'>
|
||||
{!showManualSetup ? (
|
||||
<>
|
||||
<Flex
|
||||
style={{ width: '100%' }}
|
||||
justify='space-between'
|
||||
align='center'
|
||||
gap='middle'
|
||||
>
|
||||
<Space.Compact>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={65535}
|
||||
value={scanPort}
|
||||
onChange={handlePortChange}
|
||||
style={{ width: '80px' }}
|
||||
placeholder='Port'
|
||||
/>
|
||||
<Select
|
||||
value={scanProtocol}
|
||||
onChange={handleProtocolChange}
|
||||
options={[
|
||||
{ value: 'ws', label: 'ws' },
|
||||
{ value: 'wss', label: 'wss' }
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
onClick={discoverPrinters}
|
||||
loading={discovering}
|
||||
>
|
||||
{discovering ? 'Discovering...' : 'Discover'}
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Button
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => setShowManualSetup(true)}
|
||||
>
|
||||
Manual Setup
|
||||
</Button>
|
||||
</Flex>
|
||||
<List
|
||||
dataSource={discoveredPrinters}
|
||||
renderItem={(printer) => (
|
||||
<List.Item
|
||||
key={`${printer.host}:${printer.port}`}
|
||||
actions={[
|
||||
<Radio
|
||||
key='select'
|
||||
defaultChecked={
|
||||
newPrinterFormValues.moonraker?.host ===
|
||||
printer.host
|
||||
}
|
||||
onChange={() => handlePrinterSelect(printer)}
|
||||
/>
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<Space>
|
||||
{printer.host}
|
||||
{!printer.hostname && (
|
||||
<Button
|
||||
type='text'
|
||||
icon={<EditIcon />}
|
||||
onClick={() => showEditHostnameDialog(printer)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
description={`Protocol: ${printer.protocol}, Port: ${printer.port}`}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
<Modal
|
||||
title='Edit Host'
|
||||
open={editingHostname !== null}
|
||||
onOk={() => {
|
||||
const printer = discoveredPrinters.find(
|
||||
(p) => p.host === editingHostname
|
||||
)
|
||||
if (printer) {
|
||||
handleHostnameEdit(printer, hostnameInput)
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
setEditingHostname(null)
|
||||
setHostnameInput('')
|
||||
}}
|
||||
>
|
||||
<Form.Item label='Host' required>
|
||||
<Input
|
||||
value={hostnameInput}
|
||||
onChange={(e) => setHostnameInput(e.target.value)}
|
||||
placeholder='Enter host'
|
||||
autoFocus
|
||||
/>
|
||||
</Form.Item>
|
||||
</Modal>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Flex style={{ width: '100%' }} justify='end'>
|
||||
<Button
|
||||
icon={<SearchOutlined />}
|
||||
onClick={() => setShowManualSetup(false)}
|
||||
>
|
||||
Back to Discovery
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex vertical>
|
||||
<Form.Item
|
||||
label='Protocol'
|
||||
name={['moonraker', 'protocol']}
|
||||
rules={[
|
||||
{ required: true, message: 'Protocol is required' }
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
defaultValue='ws'
|
||||
options={[
|
||||
{ value: 'ws', label: 'Websocket' },
|
||||
{ value: 'wss', label: 'Websocket Secure' }
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label='Host'
|
||||
name={['moonraker', 'host']}
|
||||
rules={[{ required: true, message: 'Host is required' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label='Port'
|
||||
name={['moonraker', 'port']}
|
||||
rules={[{ required: true, message: 'Port is required' }]}
|
||||
>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={65535}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label='API Key'
|
||||
name={['moonraker', 'apiKey']}
|
||||
rules={[{ required: false }]}
|
||||
>
|
||||
<Input.Password
|
||||
placeholder='Optional API key'
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Required',
|
||||
key: 'required',
|
||||
content: (
|
||||
<>
|
||||
<Form.Item
|
||||
label='Name'
|
||||
name='name'
|
||||
rules={[{ required: true, message: 'Name is required' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Summary',
|
||||
key: 'summary',
|
||||
content: (
|
||||
<Form.Item>
|
||||
<Descriptions column={1} items={summaryItems} size={'small'} />
|
||||
</Form.Item>
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<Flex gap={'middle'}>
|
||||
{contextHolder}
|
||||
{notificationContextHolder}
|
||||
|
||||
<div style={{ minWidth: '160px' }}>
|
||||
<Steps current={currentStep} items={steps} direction='vertical' />
|
||||
</div>
|
||||
|
||||
<Divider type={'vertical'} style={{ height: 'unset' }} />
|
||||
|
||||
<Flex vertical={'true'} style={{ flexGrow: 1 }}>
|
||||
<Title level={2} style={{ marginTop: 0 }}>
|
||||
New Printer
|
||||
</Title>
|
||||
<Form
|
||||
name='basic'
|
||||
autoComplete='off'
|
||||
form={newPrinterForm}
|
||||
onFinish={handleNewPrinter}
|
||||
onValuesChange={(changedValues) =>
|
||||
setNewPrinterFormValues((prevValues) => ({
|
||||
...prevValues,
|
||||
...changedValues
|
||||
}))
|
||||
}
|
||||
initialValues={initialNewPrinterForm}
|
||||
>
|
||||
<div style={{ minHeight: '260px' }}>{steps[currentStep].content}</div>
|
||||
|
||||
<Flex justify={'end'}>
|
||||
<Button
|
||||
style={{
|
||||
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'
|
||||
htmlType='submit'
|
||||
loading={newPrinterLoading}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Form>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
NewPrinter.propTypes = {
|
||||
onOk: PropTypes.func.isRequired,
|
||||
reset: PropTypes.bool
|
||||
}
|
||||
|
||||
export default NewPrinter
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user