Compare commits

...

4 Commits

9 changed files with 266 additions and 365 deletions

View File

@ -1,8 +1,6 @@
import { useContext, useRef, useState } from 'react' import { useContext, useRef, useState } from 'react'
import { Button, Dropdown, Flex, Modal, Space } from 'antd' import { Button, Dropdown, Flex, Modal, Space } from 'antd'
import NewPrinterProfile, { import NewPrinterProfile from './PrinterProfiles/NewPrinterProfile'
EditPrinterProfile
} from './PrinterProfiles/NewPrinterProfile'
import ColumnViewButton from '../common/ColumnViewButton' import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton' import ExportListButton from '../common/ExportListButton'
import FilterSidebarButton from '../common/FilterSidebarButton' import FilterSidebarButton from '../common/FilterSidebarButton'
@ -16,10 +14,7 @@ import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useViewMode from '../hooks/useViewMode' import useViewMode from '../hooks/useViewMode'
const PrinterProfiles = () => { const PrinterProfiles = () => {
const { deleteObject } = useContext(ApiServerContext) const { deleteObject } = useContext(ApiServerContext)
const [profileModal, setProfileModal] = useState({ const [newProfileOpen, setNewProfileOpen] = useState(false)
open: false,
profileId: null
})
const tableRef = useRef() const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('PrinterProfiles') const [viewMode, setViewMode] = useViewMode('PrinterProfiles')
const [columnVisibility, setColumnVisibility] = const [columnVisibility, setColumnVisibility] =
@ -45,17 +40,12 @@ const PrinterProfiles = () => {
if (key === 'reloadList') { if (key === 'reloadList') {
tableRef.current?.reload() tableRef.current?.reload()
} else if (key === 'newPrinterProfile') { } else if (key === 'newPrinterProfile') {
setProfileModal({ open: true, profileId: null }) setNewProfileOpen(true)
} }
} }
} }
const handleRowAction = (action, profile) => { const handleRowAction = (action, profile) => {
if (action.name === 'edit') {
setProfileModal({ open: true, profileId: profile._id })
return true
}
if (action.name === 'delete') { if (action.name === 'delete') {
Modal.confirm({ Modal.confirm({
title: 'Delete printer profile?', title: 'Delete printer profile?',
@ -112,29 +102,20 @@ const PrinterProfiles = () => {
</Flex> </Flex>
<Modal <Modal
open={profileModal.open} open={newProfileOpen}
footer={null} footer={null}
width={800} width={800}
onCancel={() => setProfileModal({ open: false, profileId: null })} onCancel={() => setNewProfileOpen(false)}
destroyOnHidden destroyOnHidden
> >
{profileModal.open && {newProfileOpen && (
(profileModal.profileId ? (
<EditPrinterProfile
profileId={profileModal.profileId}
onOk={() => {
setProfileModal({ open: false, profileId: null })
tableRef.current?.reload()
}}
/>
) : (
<NewPrinterProfile <NewPrinterProfile
onOk={() => { onOk={() => {
setProfileModal({ open: false, profileId: null }) setNewProfileOpen(false)
tableRef.current?.reload() tableRef.current?.reload()
}} }}
/> />
))} )}
</Modal> </Modal>
</> </>
) )

View File

@ -1,7 +1,6 @@
import { useEffect, useMemo, useRef, useState } from 'react' import { useMemo } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import NewObjectForm from '../../common/NewObjectForm' import NewObjectForm from '../../common/NewObjectForm'
import ObjectForm from '../../common/ObjectForm'
import ObjectInfo from '../../common/ObjectInfo' import ObjectInfo from '../../common/ObjectInfo'
import WizardView from '../../common/WizardView' import WizardView from '../../common/WizardView'
@ -144,21 +143,23 @@ const SUMMARY_PROPERTIES = [
...OPTIONAL_PROPERTIES ...OPTIONAL_PROPERTIES
] ]
const PrinterProfileWizard = ({ const NewPrinterProfile = ({ onOk, reset, defaultValues = {} }) => {
objectData, const mergedDefaultValues = useMemo(
formValid, () => ({
loading, ...PRUSA_MK3S_PROFILE_DEFAULTS,
onSubmit, ...defaultValues
editing, }),
printerId = null [defaultValues]
}) => { )
const visibleProperties = useMemo(() => {
if (!printerId) return undefined
return { printer: false, 'printer._id': false }
}, [printerId])
const steps = useMemo( return (
() => [ <NewObjectForm
type='printerProfile'
reset={reset}
defaultValues={mergedDefaultValues}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{ {
title: 'Required', title: 'Required',
key: 'required', key: 'required',
@ -172,7 +173,6 @@ const PrinterProfileWizard = ({
labelWidth='75px' labelWidth='75px'
required={true} required={true}
objectData={objectData} objectData={objectData}
visibleProperties={visibleProperties}
/> />
) )
}, },
@ -249,124 +249,34 @@ const PrinterProfileWizard = ({
isEditing={false} isEditing={false}
labelWidth='210px' labelWidth='210px'
objectData={objectData} objectData={objectData}
visibleProperties={visibleProperties}
/> />
) )
} }
], ]
[objectData, visibleProperties]
)
return ( return (
<WizardView <WizardView
sizeBarWidth='160px' sizeBarWidth='160px'
steps={steps} steps={steps}
loading={loading}
formValid={formValid}
title={editing ? 'Edit Printer Profile' : 'New Printer Profile'}
submitText={editing ? 'Save' : 'Create'}
onSubmit={onSubmit}
/>
)
}
PrinterProfileWizard.propTypes = {
objectData: PropTypes.object,
formValid: PropTypes.bool.isRequired,
loading: PropTypes.bool,
onSubmit: PropTypes.func.isRequired,
editing: PropTypes.bool,
printerId: PropTypes.string
}
const NewPrinterProfile = ({ onOk, printerId = null }) => (
<NewObjectForm
type='printerProfile'
defaultValues={{
...PRUSA_MK3S_PROFILE_DEFAULTS,
...(printerId ? { printer: printerId } : {})
}}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => (
<PrinterProfileWizard
objectData={objectData}
formValid={formValid}
loading={submitLoading} loading={submitLoading}
printerId={printerId} formValid={formValid}
title='New Printer Profile'
submitText='Create'
onSubmit={async () => { onSubmit={async () => {
const result = await handleSubmit() const result = await handleSubmit()
if (result?._id) onOk(result) if (result?._id) onOk(result)
}} }}
/> />
)} )
}}
</NewObjectForm> </NewObjectForm>
)
NewPrinterProfile.propTypes = {
onOk: PropTypes.func.isRequired,
printerId: PropTypes.string
}
export const EditPrinterProfile = ({ profileId, onOk, printerId = null }) => {
const formRef = useRef(null)
const startedEditingRef = useRef(false)
const saveRequestedRef = useRef(false)
const stateRef = useRef({})
const [formState, setFormState] = useState({
loading: true,
editLoading: false,
formValid: false,
isEditing: false,
objectData: {}
})
useEffect(() => {
if (
!formState.loading &&
!formState.isEditing &&
!startedEditingRef.current
) {
startedEditingRef.current = true
formRef.current?.startEditing()
}
}, [formState.isEditing, formState.loading])
return (
<ObjectForm
id={profileId}
type='printerProfile'
ref={formRef}
onStateChange={(nextState) => {
stateRef.current = { ...stateRef.current, ...nextState }
setFormState((current) => ({ ...current, ...nextState }))
if (saveRequestedRef.current && nextState.isEditing === false) {
saveRequestedRef.current = false
onOk(stateRef.current.objectData)
}
}}
>
{({ objectData, formValid, editLoading, handleUpdate }) => (
<PrinterProfileWizard
editing
objectData={objectData}
formValid={formValid}
loading={formState.loading || editLoading}
printerId={printerId}
onSubmit={() => {
saveRequestedRef.current = true
handleUpdate()
}}
/>
)}
</ObjectForm>
) )
} }
EditPrinterProfile.propTypes = { NewPrinterProfile.propTypes = {
profileId: PropTypes.string.isRequired,
onOk: PropTypes.func.isRequired, onOk: PropTypes.func.isRequired,
printerId: PropTypes.string reset: PropTypes.bool,
defaultValues: PropTypes.object
} }
export default NewPrinterProfile export default NewPrinterProfile

View File

@ -1,9 +1,7 @@
import { useRef, useState } from 'react' import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import { Space, Flex, Card, Button, Modal } from 'antd' import { Space, Flex, Card, Modal } from 'antd'
import NewPrinterProfile, { import NewPrinterProfile from '../PrinterProfiles/NewPrinterProfile'
EditPrinterProfile
} from '../PrinterProfiles/NewPrinterProfile'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel' import loglevel from 'loglevel'
import config from '../../../../config.js' import config from '../../../../config.js'
@ -25,7 +23,6 @@ import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import PlusIcon from '../../../Icons/PlusIcon.jsx'
import PrinterProfileIcon from '../../../Icons/PrinterProfileIcon.jsx' import PrinterProfileIcon from '../../../Icons/PrinterProfileIcon.jsx'
const log = loglevel.getLogger('PrinterInfo') const log = loglevel.getLogger('PrinterInfo')
log.setLevel(config.logLevel) log.setLevel(config.logLevel)
@ -52,15 +49,7 @@ const PrinterInfo = () => {
loading: false, loading: false,
objectData: {} objectData: {}
}) })
const [profileModal, setProfileModal] = useState({ const [newPrinterProfileOpen, setNewPrinterProfileOpen] = useState(false)
open: false,
profileId: null
})
const handleProfileSaved = () => {
setProfileModal({ open: false, profileId: null })
profilesTableRef.current?.reload()
}
const actions = { const actions = {
edit: () => { edit: () => {
@ -74,6 +63,10 @@ const PrinterInfo = () => {
finishEdit: () => { finishEdit: () => {
objectFormRef?.current.handleUpdate() objectFormRef?.current.handleUpdate()
return true return true
},
newPrinterProfile: () => {
setNewPrinterProfileOpen(true)
return true
} }
} }
@ -196,17 +189,6 @@ const PrinterInfo = () => {
{objectFormState.loading ? ( {objectFormState.loading ? (
<InfoCollapsePlaceholder /> <InfoCollapsePlaceholder />
) : ( ) : (
<Flex vertical gap='middle'>
<Flex>
<Button
icon={<PlusIcon />}
onClick={() =>
setProfileModal({ open: true, profileId: null })
}
>
New Printer Profile
</Button>
</Flex>
<ObjectTable <ObjectTable
ref={profilesTableRef} ref={profilesTableRef}
type='printerProfile' type='printerProfile'
@ -215,18 +197,7 @@ const PrinterInfo = () => {
printer: false, printer: false,
'printer._id': false 'printer._id': false
}} }}
onRowAction={(action, profile) => {
if (action.name === 'edit') {
setProfileModal({
open: true,
profileId: profile._id
})
return true
}
return false
}}
/> />
</Flex>
)} )}
</InfoCollapse> </InfoCollapse>
@ -265,25 +236,25 @@ const PrinterInfo = () => {
</ScrollBox> </ScrollBox>
</Flex> </Flex>
<Modal <Modal
open={profileModal.open} open={newPrinterProfileOpen}
styles={{ content: { paddingBottom: '24px' } }}
footer={null} footer={null}
width={1000} width={800}
onCancel={() => setProfileModal({ open: false, profileId: null })} onCancel={() => {
destroyOnHidden setNewPrinterProfileOpen(false)
}}
destroyOnHidden={true}
> >
{profileModal.open &&
(profileModal.profileId ? (
<EditPrinterProfile
profileId={profileModal.profileId}
printerId={printerId}
onOk={handleProfileSaved}
/>
) : (
<NewPrinterProfile <NewPrinterProfile
printerId={printerId} onOk={() => {
onOk={handleProfileSaved} setNewPrinterProfileOpen(false)
profilesTableRef.current?.reload()
}}
reset={newPrinterProfileOpen}
defaultValues={{
printer: { ...objectFormState.objectData }
}}
/> />
))}
</Modal> </Modal>
</> </>
) )

View File

@ -24,7 +24,8 @@ const FileList = ({
showDownload = true, showDownload = true,
defaultPreviewOpen = false, defaultPreviewOpen = false,
minimal = false, minimal = false,
card = true card = true,
maxWidth = '100%'
}) => { }) => {
const { fetchFileContent, flushFile } = useContext(ApiServerContext) const { fetchFileContent, flushFile } = useContext(ApiServerContext)
const navigate = useNavigate() const navigate = useNavigate()
@ -63,12 +64,23 @@ const FileList = ({
const filesToRender = multiple ? files : [files] const filesToRender = multiple ? files : [files]
const renderFileContent = (file) => ( const renderFileContent = (file) => (
<Flex vertical gap='10px' style={{ maxWidth: '100%', minWidth: 0 }}> <Flex
vertical
gap='10px'
style={{ maxWidth: '100%', minWidth: 0, width: card ? '100%' : 'fit-content' }}
>
<Flex <Flex
justify={card ? 'space-between' : 'start'} justify={card ? 'space-between' : 'start'}
style={{ maxWidth: '100%', minWidth: 0 }} align='center'
gap='small'
wrap={false}
style={{ maxWidth: '100%', minWidth: 0, width: card ? '100%' : 'fit-content' }}
>
<Flex
gap={'small'}
align='center'
style={{ minWidth: 0, flex: '0 1 auto', overflow: 'hidden' }}
> >
<Flex gap={'small'} align='center' style={{ minWidth: 0 }}>
<FileIcon <FileIcon
style={{ margin: 0, fontSize: card == true ? '24px' : '16px' }} style={{ margin: 0, fontSize: card == true ? '24px' : '16px' }}
/> />
@ -76,10 +88,11 @@ const FileList = ({
<Text style={{ marginTop: '1px', minWidth: 0 }} ellipsis> <Text style={{ marginTop: '1px', minWidth: 0 }} ellipsis>
{file.name || file.filename || 'Unknown file'} {file.name || file.filename || 'Unknown file'}
</Text> </Text>
<Tag>{file.extension}</Tag> {file.extension && <Tag style={{ margin: 0 }}>{file.extension}</Tag>}
</Flex> </Flex>
{!minimal && (
<Flex gap={'small'} align='center'> <Flex gap={'small'} align='center'>
{showDownload && !minimal && ( {showDownload && (
<Button <Button
icon={<DownloadIcon />} icon={<DownloadIcon />}
size='small' size='small'
@ -87,7 +100,7 @@ const FileList = ({
onClick={() => handleDownload(file)} onClick={() => handleDownload(file)}
/> />
)} )}
{showPreview && !minimal && ( {showPreview && (
<Button <Button
icon={previewOpen ? <EyeSlashIcon /> : <EyeIcon />} icon={previewOpen ? <EyeSlashIcon /> : <EyeIcon />}
size='small' size='small'
@ -101,7 +114,7 @@ const FileList = ({
}} }}
/> />
)} )}
{showInfo && !minimal && ( {showInfo && (
<Button <Button
icon={<InfoCircleIcon />} icon={<InfoCircleIcon />}
size='small' size='small'
@ -111,7 +124,7 @@ const FileList = ({
}} }}
/> />
)} )}
{editing && !minimal && ( {editing && (
<Button <Button
icon={<BinIcon />} icon={<BinIcon />}
size='small' size='small'
@ -120,6 +133,7 @@ const FileList = ({
/> />
)} )}
</Flex> </Flex>
)}
</Flex> </Flex>
{previewOpen && !minimal ? ( {previewOpen && !minimal ? (
<> <>
@ -130,8 +144,16 @@ const FileList = ({
</Flex> </Flex>
) )
const rootStyle = {
minWidth: 0,
maxWidth,
...(card
? { width: '100%' }
: { width: 'fit-content', alignSelf: 'flex-start' })
}
return ( return (
<div style={{ width: '100%' }}> <div style={rootStyle}>
{filesToRender.map((file, index) => { {filesToRender.map((file, index) => {
const key = file._id || file.id || index const key = file._id || file.id || index
const style = { const style = {
@ -176,7 +198,8 @@ FileList.propTypes = {
showDownload: PropTypes.bool, showDownload: PropTypes.bool,
defaultPreviewOpen: PropTypes.bool, defaultPreviewOpen: PropTypes.bool,
card: PropTypes.bool, card: PropTypes.bool,
minimal: PropTypes.bool minimal: PropTypes.bool,
maxWidth: PropTypes.string
} }
export default FileList export default FileList

View File

@ -333,10 +333,10 @@ const ObjectChildTable = ({
canAddRemove, canAddRemove,
itemsSource, itemsSource,
onChange, onChange,
rowKey,
getResolvedRecordKey, getResolvedRecordKey,
handleOpenDetailModal, handleOpenDetailModal,
handleRemoveItem handleRemoveItem,
objectData
]) ])
const skeletonData = useMemo(() => { const skeletonData = useMemo(() => {

View File

@ -616,6 +616,7 @@ const ObjectProperty = ({
minimal={minimal} minimal={minimal}
multiple={false} multiple={false}
card={false} card={false}
maxWidth={maxWidth}
defaultPreviewOpen={previewOpen} defaultPreviewOpen={previewOpen}
showPreview={showPreview} showPreview={showPreview}
showInfo={showHyperlink} showInfo={showHyperlink}
@ -630,6 +631,7 @@ const ObjectProperty = ({
files={value} files={value}
minimal={minimal} minimal={minimal}
multiple={true} multiple={true}
maxWidth={maxWidth}
defaultPreviewOpen={previewOpen} defaultPreviewOpen={previewOpen}
showPreview={showPreview} showPreview={showPreview}
showInfo={showHyperlink} showInfo={showHyperlink}

View File

@ -50,7 +50,7 @@ const logger = loglevel.getLogger('DasboardTable')
logger.setLevel(config.logLevel) logger.setLevel(config.logLevel)
const SCROLL_THRESHOLD = 50 const SCROLL_THRESHOLD = 50
const SKELETON_HEIGHT = 49 const SKELETON_HEIGHT = 49.5
const RowForm = ({ record, isEditing, onRegister, children }) => { const RowForm = ({ record, isEditing, onRegister, children }) => {
const [form] = Form.useForm() const [form] = Form.useForm()
@ -966,7 +966,11 @@ const ObjectTable = forwardRef(
render: (text, record) => { render: (text, record) => {
if (record?.isSkeleton) { if (record?.isSkeleton) {
return ( return (
<Skeleton.Input active size='small' style={{ width: '100%' }} /> <Skeleton.Input
active
size='small'
style={{ width: '100%', height: 24.5 }}
/>
) )
} }
return ( return (

View File

@ -53,7 +53,7 @@ const PropertyChanges = ({ type, value }) => {
} }
return ( return (
<Descriptions.Item key={key} label={changeProperty.label}> <Descriptions.Item key={key} label={changeProperty.label}>
<Flex gap={'small'}> <Flex gap={'small'} style={{ minWidth: 0, width: '100%' }}>
{value?.old ? ( {value?.old ? (
<ObjectProperty <ObjectProperty
{...changeProperty} {...changeProperty}

View File

@ -10,6 +10,7 @@ import StopCircleIcon from '../../components/Icons/StopCircleIcon'
import FilamentStockIcon from '../../components/Icons/FilamentStockIcon' import FilamentStockIcon from '../../components/Icons/FilamentStockIcon'
import ControlIcon from '../../components/Icons/ControlIcon' import ControlIcon from '../../components/Icons/ControlIcon'
import JobIcon from '../../components/Icons/JobIcon' import JobIcon from '../../components/Icons/JobIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
export const Printer = { export const Printer = {
name: 'printer', name: 'printer',
@ -67,6 +68,15 @@ export const Printer = {
} }
}, },
{ type: 'divider' }, { type: 'divider' },
{
name: 'newPrinterProfile',
label: 'New Printer Profile',
type: 'button',
icon: PlusIcon,
url: (_id) =>
`/dashboard/production/printers/info?printerId=${_id}&action=newPrinterProfile`
},
{ type: 'divider' },
{ {
name: 'restartSubmenu', name: 'restartSubmenu',
label: 'Restart', label: 'Restart',