Enhance Dashboard Components with New Features and Styling Improvements

- Updated App.css to refine styling for object display tags and introduced a border for better visual distinction.
- Enhanced NewDocumentJob component by adding a default quantity value for improved user experience.
- Introduced PrinterQueue component to manage and display printer job queues, enhancing the ControlPrinter functionality.
- Improved MissingPlaceholder component with additional props for customizable border and padding options.
- Adjusted ObjectDisplay and ObjectList components for better layout and interaction, including cursor changes for clickable elements.
- Updated ThemeContext to manage new color variables for consistent theming across components.
This commit is contained in:
Tom Butcher 2026-08-22 20:02:50 +01:00
parent 39f1b31ac8
commit 90d9ed286d
11 changed files with 186 additions and 55 deletions

View File

@ -680,9 +680,11 @@ body {
border-radius: 0px !important;
}
.ant-select-selection-item .object-display-tag,
.ant-select-tree-title .object-display-tag {
.ant-select-selection-item .ant-tag.object-display-tag,
.ant-select-tree-title .ant-tag.object-display-tag,
.ant-select-dropdown .ant-tag.object-display-tag {
background: transparent !important;
border: none;
padding: 0;
margin-right: 1px !important;
}
@ -1304,7 +1306,8 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
min-width: 0;
max-width: 100%;
width: auto;
overflow: hidden;
overflow-y: hidden;
overflow-x: visible;
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
@ -1686,6 +1689,10 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
font-size: 95%;
}
.ant-tag.object-display-tag {
border: 1px solid color-mix(in srgb, var(--color-text) 5%, transparent);
}
.code-block-editor .simplebar-content-wrapper {
background-color: var(--layout-modal-bg);
}

View File

@ -73,6 +73,7 @@ const NewDocumentJob = ({ onOk, defaultValues = {} }) => {
// Capture initial default values so later prop changes don't re-initialize the form
const defaultValuesRef = useRef({
objectType: 'documentJob',
quantity: 1,
...defaultValues,
saveToFile: false
})

View File

@ -34,6 +34,8 @@ import { ApiServerContext } from '../../context/ApiServerContext.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import PrinterControlButtons from '../../common/PrinterControlButtons.jsx'
import PrinterQueue from '../../common/PrinterQueue.jsx'
import ListIcon from '../../../Icons/ListIcon.jsx'
const log = loglevel.getLogger('ControlPrinter')
log.setLevel(config.logLevel)
@ -50,6 +52,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
printer: true,
job: true,
subjob: true,
queue: true,
filamentStock: true,
temperature: true,
position: true,
@ -297,6 +300,10 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
key: 'subJob',
label: 'Sub Job'
},
{
key: 'queue',
label: 'Queue'
},
{
key: 'filamentStock',
label: 'Filament Stock'
@ -401,6 +408,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
'currentJob._id': false,
currentSubJob: false,
'currentSubJob._id': false,
queue: false,
createdAt: false,
updatedAt: false,
state: !slicerIntegration,
@ -418,6 +426,29 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
}}
</ObjectForm>
</InfoCollapse>
<InfoCollapse
title={'Queue'}
icon={<ListIcon />}
collapseKey='queue'
active={collapseState.queue}
onToggle={(expanded) =>
updateCollapseState('queue', expanded)
}
>
<Card
style={{
backgroundColor: 'transparent',
border: '1px solid var(--color-descriptions-border)'
}}
>
<PrinterQueue
queue={objectFormState.objectData?.queue}
currentSubJob={
objectFormState.objectData?.currentSubJob
}
/>
</Card>
</InfoCollapse>
<InfoCollapse
title={'Job'}
icon={<JobIcon />}
@ -459,7 +490,9 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
) : (
<MissingPlaceholder
message={'No job.'}
padding='24px'
hasBackground={false}
mutedBorder={true}
/>
)}
</InfoCollapse>
@ -507,7 +540,9 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
) : (
<MissingPlaceholder
message={'No sub job.'}
padding='24px'
hasBackground={false}
mutedBorder={true}
/>
)}
</InfoCollapse>
@ -552,7 +587,9 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
) : (
<MissingPlaceholder
message={'No filament stock.'}
padding='24px'
hasBackground={false}
mutedBorder={true}
/>
)}
</InfoCollapse>

View File

@ -7,19 +7,27 @@ const { Text } = Typography
const MissingPlaceholder = ({
message,
hasBackground = true,
hasBorder = true
hasBorder = true,
mutedBorder = false,
padding = '16px'
}) => {
var border = undefined
if (hasBorder == false) {
border = 'none'
} else if (mutedBorder == true && hasBorder == true) {
border = '1px solid var(--color-descriptions-border)'
}
return (
<Card
size='small'
style={{
background: hasBackground == false ? 'transparent' : undefined,
border:
hasBorder == false
? 'none'
: hasBackground == false
? '1px solid rgb(0 0 0 / 7%)'
: undefined
border: border
}}
styles={{
body: {
padding: padding
}
}}
>
<Flex
@ -42,7 +50,9 @@ const MissingPlaceholder = ({
MissingPlaceholder.propTypes = {
message: PropTypes.string.isRequired,
hasBackground: PropTypes.bool,
hasBorder: PropTypes.bool
hasBorder: PropTypes.bool,
mutedBorder: PropTypes.bool,
padding: PropTypes.string
}
export default MissingPlaceholder

View File

@ -13,7 +13,7 @@ import ElipsisText from './ElipsisText'
import SpotlightTooltip from './SpotlightTooltip'
import StateTag from './StateTag'
const { Text, Link } = Typography
const { Text } = Typography
const ObjectDisplay = ({
object,
@ -159,7 +159,6 @@ const ObjectDisplay = ({
<Tag
style={{
margin: 0,
border: 'none',
minWidth: 0,
maxWidth: '100%'
}}
@ -187,7 +186,7 @@ const ObjectDisplay = ({
hyperlink = buildActionUrl(model, defaultModelActions[0], objectId, '', '')
}
// Render name with hyperlink/spotlight support
// Render name with spotlight support
const renderNameDisplay = () => {
if (!objectData?.name) return null
@ -195,14 +194,6 @@ const ObjectDisplay = ({
<ElipsisText style={{ lineHeight: '1' }}>{objectData.name}</ElipsisText>
)
// If hyperlink is enabled
if (showHyperlink && hyperlink != null) {
const linkElement = (
<Link onClick={() => navigate(hyperlink)} ellipsis>
{textElement}
</Link>
)
if (showSpotlight && objectId) {
return (
<Popover
@ -216,27 +207,6 @@ const ObjectDisplay = ({
placement='topLeft'
arrow={false}
style={{ padding: 0 }}
>
{linkElement}
</Popover>
)
}
return linkElement
}
// If hyperlink is disabled
if (showSpotlight && objectId) {
return (
<Popover
content={
<SpotlightTooltip
query={prefix + ':' + objectId}
type={objectType}
/>
}
trigger={['hover', 'click']}
placement='topLeft'
arrow={false}
>
{textElement}
</Popover>
@ -245,20 +215,23 @@ const ObjectDisplay = ({
return textElement
}
const canNavigate = showHyperlink && hyperlink != null
return (
<Tag
style={{
margin: 0,
border: 'none',
minWidth: 0,
maxWidth: '100%'
maxWidth: '100%',
cursor: canNavigate ? 'pointer' : undefined
}}
className='object-display-tag'
onClick={canNavigate ? () => navigate(hyperlink) : undefined}
>
<Flex
gap={objectData?.color || objectData?.state ? 'small' : '5px'}
align='center'
style={{ minWidth: 0, height: '24px' }}
style={{ minWidth: 0, height: '22px' }}
>
<Icon />
<Flex gap={'small'} align='center' style={{ minWidth: 0, flex: 1 }}>
@ -285,7 +258,7 @@ const ObjectDisplay = ({
type={objectType}
longId={false}
showCopy={false}
showHyperlink={showHyperlink}
showHyperlink={false}
showSpotlight={showSpotlight}
/>
) : null}

View File

@ -17,7 +17,15 @@ const ObjectList = ({
}
const listContents = (
<Flex gap={'4px'} wrap={!scrollHorizontal} style={style} justify={'start'}>
<Flex
gap={'4px'}
wrap={!scrollHorizontal}
style={{
...style,
width: 'fit-content'
}}
justify={'start'}
>
{value.map((item) => (
<div key={item._id} style={{ flexShrink: 0 }}>
<ObjectDisplay
@ -31,7 +39,11 @@ const ObjectList = ({
)
if (scrollHorizontal) {
return <ScrollBox horizontalBottomPadding={10}>{listContents}</ScrollBox>
return (
<ScrollBox horizontalBottomPadding={9}>
{listContents}
</ScrollBox>
)
} else {
return listContents
}

View File

@ -944,6 +944,11 @@ const ObjectTable = forwardRef(
if (connected !== true) return
if (subscribedTypeRef.current === type) return
console.log(
'subscribing to object type updates',
type,
subscriptionFilterRef.current
)
const unsubscribe = subscribeToObjectTypeUpdatesFnRef.current(
type,
subscriptionFilterRef.current,

View File

@ -0,0 +1,65 @@
import PropTypes from 'prop-types'
import ObjectDisplay from './ObjectDisplay'
import { Steps } from 'antd'
import MissingPlaceholder from './MissingPlaceholder'
const getId = (item) => {
if (!item) return null
if (typeof item === 'string') return item
if (typeof item._id === 'string') return item._id
if (typeof item._id === 'object' && item._id?._id) return item._id._id
return null
}
const toObject = (item) => {
if (!item) return null
if (typeof item === 'string') return { _id: item }
return item
}
const PrinterQueue = ({ queue, currentSubJob }) => {
const queueItems = Array.isArray(queue)
? queue.map(toObject).filter(Boolean)
: []
const current = toObject(currentSubJob)
const currentId = getId(current)
const items = []
if (current && !queueItems.some((item) => getId(item) === currentId)) {
items.push(current)
}
items.push(...queueItems)
if (items.length === 0) {
return (
<MissingPlaceholder
message='No sub jobs in queue.'
hasBackground={false}
hasBorder={false}
padding={0}
/>
)
}
const currentIndex = currentId
? items.findIndex((item) => getId(item) === currentId)
: -1
return (
<Steps
current={currentIndex}
direction='vertical'
size='small'
items={items.map((item) => ({
title: <ObjectDisplay object={item} objectType='subJob' showHyperlink />
}))}
/>
)
}
PrinterQueue.propTypes = {
queue: PropTypes.array,
currentSubJob: PropTypes.oneOfType([PropTypes.object, PropTypes.string])
}
export default PrinterQueue

View File

@ -9,6 +9,7 @@ const ScrollBox = ({
verticalRightPadding = 16,
inner = false,
smallPadding = false,
className = '',
...rest
}) => {
return (
@ -19,7 +20,7 @@ const ScrollBox = ({
'--scrollbox-vertical-right-padding': `${verticalRightPadding}px`,
'--scrollbox-horizontal-bottom-padding': `${horizontalBottomPadding}px`
}}
className={`${inner ? 'scrollbox-inner' : ''} ${smallPadding ? 'scrollbox-inner-small-padding' : ''}`}
className={`${inner ? 'scrollbox-inner' : ''} ${smallPadding ? 'scrollbox-inner-small-padding' : ''} ${className}`}
>
<SimpleBar style={{ height: '100%', ...style }} {...rest}>
{children}
@ -34,7 +35,8 @@ ScrollBox.propTypes = {
horizontalBottomPadding: PropTypes.number,
verticalRightPadding: PropTypes.number,
inner: PropTypes.bool,
smallPadding: PropTypes.bool
smallPadding: PropTypes.bool,
className: PropTypes.string
}
export default ScrollBox

View File

@ -157,6 +157,10 @@ export const ThemeProvider = ({ children }) => {
isDarkMode ? '#1f1f1f' : '#ffffff'
)
root.style.setProperty('--color-text', isDarkMode ? '#ffffff' : '#000000')
root.style.setProperty(
'--color-descriptions-border',
isDarkMode ? 'rgba(253,253,253,0.12)' : 'rgba(5,5,5,0.06)'
)
}, [isDarkMode, primaryColorOverride])
const themeConfig = {

View File

@ -88,9 +88,10 @@ export const DocumentJob = {
content: () => createElement(DocumentJobInfo)
}
],
columns: ['_reference', 'name', 'state', 'createdAt', 'updatedAt'],
columns: ['_reference', 'name', 'quantity', 'state', 'createdAt', 'updatedAt'],
filters: [
'name',
'quantity',
'width',
'height',
'state',
@ -98,7 +99,7 @@ export const DocumentJob = {
'updatedAt',
'_reference'
],
sorters: ['name', 'state', 'createdAt', 'updatedAt'],
sorters: ['name', 'quantity', 'state', 'createdAt', 'updatedAt'],
properties: [
{
name: '_id',
@ -203,6 +204,20 @@ export const DocumentJob = {
online: true
}
}
},
{
name: 'quantity',
label: 'Quantity',
type: 'number',
required: true,
min: 1,
step: 1,
columnWidth: 125,
value: (objectData) => {
if (objectData?.createdAt == undefined && objectData?.quantity == null) {
return 1
}
}
}
]
}