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