466 lines
13 KiB
JavaScript
466 lines
13 KiB
JavaScript
import React from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import {
|
|
Typography,
|
|
Badge,
|
|
Input,
|
|
InputNumber,
|
|
Form,
|
|
Select,
|
|
DatePicker,
|
|
Switch
|
|
} from 'antd'
|
|
import VendorSelect from './VendorSelect'
|
|
import FilamentSelect from './FilamentSelect'
|
|
import IdDisplay from './IdDisplay'
|
|
import TimeDisplay from './TimeDisplay'
|
|
import dayjs from 'dayjs'
|
|
import PrinterSelect from './PrinterSelect'
|
|
import GCodeFileSelect from './GCodeFileSelect'
|
|
import PartSelect from './PartSelect'
|
|
import EmailDisplay from '../../Icons/EmailDisplay'
|
|
import UrlDisplay from '../../Icons/UrlDisplay'
|
|
import CountryDisplay from './CountryDisplay'
|
|
import CountrySelect from './CountrySelect'
|
|
import TagsDisplay from './TagsDisplay'
|
|
import TagsInput from './TagsInput'
|
|
import BoolDisplay from './BoolDisplay'
|
|
import PrinterState from './PrinterState'
|
|
import SubJobState from './SubJobState'
|
|
import JobState from './JobState'
|
|
import ColorSelector from './ColorSelector'
|
|
import SecretDisplay from './SecretDisplay'
|
|
import EyeIcon from '../../Icons/EyeIcon'
|
|
import EyeSlashIcon from '../../Icons/EyeSlashIcon'
|
|
|
|
const { Text } = Typography
|
|
|
|
const MATERIAL_OPTIONS = [
|
|
{ value: 'PLA', label: 'PLA' },
|
|
{ value: 'PETG', label: 'PETG' },
|
|
{ value: 'ABS', label: 'ABS' },
|
|
{ value: 'ASA', label: 'ASA' },
|
|
{ value: 'HIPS', label: 'HIPS' },
|
|
{ value: 'TPU', label: 'TPU' }
|
|
]
|
|
|
|
const ObjectProperty = ({
|
|
type = 'text',
|
|
value,
|
|
isEditing = false,
|
|
formItemProps = {},
|
|
required = false,
|
|
name,
|
|
label,
|
|
showLabel = false,
|
|
objectType = 'unknown',
|
|
readOnly = false,
|
|
...rest
|
|
}) => {
|
|
const renderProperty = () => {
|
|
console.log('Rendering')
|
|
if (!isEditing || readOnly) {
|
|
switch (type) {
|
|
case 'secret':
|
|
if (value != null) {
|
|
return <SecretDisplay value={value} {...rest} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
case 'wsprotocol':
|
|
switch (value) {
|
|
case 'ws':
|
|
return <Text>Websocket</Text>
|
|
case 'wss':
|
|
return <Text>Websocket Secure</Text>
|
|
default:
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
case 'bool': {
|
|
if (value != null) {
|
|
return <BoolDisplay value={value} yesNo={true} {...rest} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'dateTime': {
|
|
if (value != null) {
|
|
return <TimeDisplay dateTime={value} {...rest} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'currency': {
|
|
if (value != null) {
|
|
return <Text>{`£${value}/kg`}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'country': {
|
|
if (value != null) {
|
|
return <CountryDisplay countryCode={value} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'color': {
|
|
if (value) {
|
|
return <Badge color={value} text={value} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'weight': {
|
|
if (value != null) {
|
|
return <Text>{`${value}g`}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'number': {
|
|
if (value != null) {
|
|
return <Text>{value}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'text':
|
|
if (value != null && value != '') {
|
|
return <Text>{value}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
case 'email':
|
|
if (value != null && value != '') {
|
|
return <EmailDisplay email={value} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
case 'url':
|
|
if (value != null && value != '') {
|
|
return <UrlDisplay url={value} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
case 'object': {
|
|
if (value && value.name) {
|
|
return <Text>{value.name}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'state': {
|
|
if (value && value?.state) {
|
|
switch (objectType) {
|
|
case 'printer':
|
|
return <PrinterState printer={value} {...rest} />
|
|
case 'job':
|
|
return <JobState job={value} {...rest} />
|
|
case 'subjob':
|
|
return <SubJobState subJob={value} {...rest} />
|
|
|
|
default:
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'material': {
|
|
if (value) {
|
|
return <Text>{value}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'id': {
|
|
if (value) {
|
|
return <IdDisplay id={value} type={objectType} {...rest} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'density': {
|
|
if (value != null) {
|
|
return <Text>{`${value} g/cm³`}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'mm': {
|
|
if (value != null) {
|
|
return <Text>{`${value} mm`}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'tags': {
|
|
if (value != null || value?.length != 0) {
|
|
return <TagsDisplay tags={value} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
case 'version': {
|
|
if (value != null) {
|
|
return <Text>{`${value} mm`}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
default: {
|
|
if (value) {
|
|
return <Text>{value}</Text>
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Editable mode: wrap in Form.Item
|
|
// Merge required rule if needed
|
|
let mergedFormItemProps = { ...formItemProps }
|
|
if (required) {
|
|
let rules
|
|
if (mergedFormItemProps.rules) {
|
|
rules = [...mergedFormItemProps.rules]
|
|
} else {
|
|
rules = []
|
|
}
|
|
const hasRequiredRule = rules.some((rule) => rule && rule.required)
|
|
if (!hasRequiredRule) {
|
|
rules.push({ required: true, message: 'This field is required' })
|
|
}
|
|
mergedFormItemProps.rules = rules
|
|
}
|
|
// Remove name from mergedFormItemProps if present
|
|
if (mergedFormItemProps.name) {
|
|
delete mergedFormItemProps.name
|
|
}
|
|
// If label is provided, set it on Form.Item
|
|
if (label && showLabel == true) {
|
|
mergedFormItemProps.label = label
|
|
}
|
|
// Always apply style: { margin: 0 } unless overridden
|
|
mergedFormItemProps.style = {
|
|
margin: 0,
|
|
...(mergedFormItemProps.style || {})
|
|
}
|
|
switch (type) {
|
|
case 'secret':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<Input.Password
|
|
placeholder={label}
|
|
{...mergedFormItemProps}
|
|
iconRender={(visible) =>
|
|
visible ? <EyeSlashIcon /> : <EyeIcon />
|
|
}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'wsprotocol':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<Select
|
|
defaultValue='ws'
|
|
options={[
|
|
{ value: 'ws', label: 'Websocket' },
|
|
{ value: 'wss', label: 'Websocket Secure' }
|
|
]}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'bool':
|
|
return (
|
|
<Form.Item
|
|
name={name}
|
|
{...mergedFormItemProps}
|
|
valuePropName='checked'
|
|
>
|
|
<Switch />
|
|
</Form.Item>
|
|
)
|
|
case 'dateTime':
|
|
return (
|
|
<Form.Item
|
|
name={name}
|
|
{...mergedFormItemProps}
|
|
getValueProps={(v) => ({ value: v ? dayjs(v) : null })}
|
|
>
|
|
<DatePicker showTime style={{ width: '100%' }} />
|
|
</Form.Item>
|
|
)
|
|
case 'currency':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
prefix='£'
|
|
suffix='/kg'
|
|
style={{ width: '100%' }}
|
|
placeholder={label}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'country':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<CountrySelect />
|
|
</Form.Item>
|
|
)
|
|
case 'color':
|
|
return (
|
|
<Form.Item
|
|
name={name}
|
|
{...mergedFormItemProps}
|
|
valuePropName='value'
|
|
getValueFromEvent={(v) => v}
|
|
>
|
|
<ColorSelector required={required} />
|
|
</Form.Item>
|
|
)
|
|
case 'weight':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
suffix='g'
|
|
style={{ width: '100%' }}
|
|
placeholder={label}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'number':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
style={{ width: '100%' }}
|
|
placeholder={label}
|
|
{...mergedFormItemProps}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'text':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<Input placeholder={label} {...mergedFormItemProps} />
|
|
</Form.Item>
|
|
)
|
|
case 'material':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<Select options={MATERIAL_OPTIONS} placeholder={label} />
|
|
</Form.Item>
|
|
)
|
|
case 'id':
|
|
// id is not editable, just show view mode
|
|
if (value) {
|
|
return <IdDisplay id={value} type={objectType} {...rest} />
|
|
} else {
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
case 'object':
|
|
switch (objectType) {
|
|
case 'vendor':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<VendorSelect placeholder={label} />
|
|
</Form.Item>
|
|
)
|
|
case 'printer':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<PrinterSelect placeholder={label} />
|
|
</Form.Item>
|
|
)
|
|
case 'gcodefile':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<GCodeFileSelect placeholder={label} />
|
|
</Form.Item>
|
|
)
|
|
case 'filament':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<FilamentSelect placeholder={label} />
|
|
</Form.Item>
|
|
)
|
|
case 'part':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<PartSelect placeholder={label} />
|
|
</Form.Item>
|
|
)
|
|
default:
|
|
return <Text type='secondary'>n/a</Text>
|
|
}
|
|
|
|
case 'density':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
suffix='g/cm³'
|
|
style={{ width: '100%' }}
|
|
placeholder={label}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'mm':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<InputNumber
|
|
suffix='mm'
|
|
style={{ width: '100%' }}
|
|
placeholder={label}
|
|
/>
|
|
</Form.Item>
|
|
)
|
|
case 'tags':
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<TagsInput />
|
|
</Form.Item>
|
|
)
|
|
default:
|
|
return (
|
|
<Form.Item name={name} {...mergedFormItemProps}>
|
|
<Input placeholder={label} {...mergedFormItemProps} />
|
|
</Form.Item>
|
|
)
|
|
}
|
|
}
|
|
|
|
const property = renderProperty()
|
|
// Render the property directly (remove useDescriptions functionality)
|
|
return property
|
|
}
|
|
|
|
ObjectProperty.propTypes = {
|
|
type: PropTypes.oneOf([
|
|
'text',
|
|
'number',
|
|
'currency',
|
|
'color',
|
|
'weight',
|
|
'vendor',
|
|
'material',
|
|
'id',
|
|
'density',
|
|
'mm'
|
|
]),
|
|
value: PropTypes.any,
|
|
isEditing: PropTypes.bool,
|
|
formItemProps: PropTypes.object,
|
|
required: PropTypes.bool,
|
|
name: PropTypes.string,
|
|
label: PropTypes.string,
|
|
showLabel: PropTypes.bool,
|
|
objectType: PropTypes.string.isRequired,
|
|
readOnly: PropTypes.bool
|
|
}
|
|
|
|
ObjectProperty.defaultProps = {}
|
|
|
|
export default ObjectProperty
|