Compare commits

...

6 Commits

Author SHA1 Message Date
d982b8dfb3 Refactor components to improve logging and ID handling
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Removed console.log statements from FilePreview and ObjectForm components for cleaner code.
- Replaced console.log with loglevel in KeyboardShortcut for better logging management.
- Updated ID handling in ObjectForm, ObjectTable, PrinterMiscPanel, PrinterPositionPanel, and PrinterTemperaturePanel components to ensure IDs are consistently converted to lowercase, enhancing data integrity.
2026-08-01 16:18:37 +01:00
a2d27c692a Add PrinterControlButtons component for enhanced printer action management
- Introduced PrinterControlButtons to encapsulate start, pause, and cancel actions for printers, improving user interaction.
- Updated ControlPrinter component to integrate PrinterControlButtons, enhancing layout with Flex for better alignment.
- Adjusted button states based on printer status and loading state, ensuring appropriate user feedback and action availability.
2026-08-01 15:54:00 +01:00
e3544ef2e2 Refactor PrinterPositionPanel and PrinterTemperaturePanel for improved styling and layout
- Introduced controlLabelStyle and controlInputStyle for consistent styling of control elements in PrinterPositionPanel.
- Replaced Space components with Flex for better alignment and layout management in PrinterPositionPanel.
- Updated input field widths in PrinterTemperaturePanel for enhanced usability and consistency.
2026-08-01 15:37:51 +01:00
e52798d4ac Enhance printer panels to handle offline state more effectively
- Added an `offline` prop to PrinterTemperaturePanel, PrinterMiscPanel, and PrinterPositionPanel components to manage state when printers are offline.
- Implemented useEffect hooks in each panel to reset relevant data when offline, ensuring accurate display of printer status.
- Updated ControlPrinter component to pass the `offline` state based on the printer's online status, improving user experience during offline scenarios.
2026-08-01 15:28:48 +01:00
f3d6bebc38 Integrate TableStateProvider into App and Dashboard components for enhanced state management
- Added TableStateProvider to the App component, encapsulating routing logic to manage table states effectively.
- Removed redundant TableStateProvider from the DashboardLayout component, streamlining the component structure.
- Updated ControlPrinter and ObjectActions components to improve action visibility and navigation button rendering based on slicer integration state.
- Enhanced action filtering in ObjectActions to eliminate unnecessary dividers, improving menu item clarity.
2026-08-01 15:21:58 +01:00
12cae32e33 Enhance Thumbnail and ApiServerContext components for improved functionality
- Removed the default border style from the Thumbnail component to streamline its appearance.
- Updated the Thumbnail component to dynamically set the width in the card styles, enhancing layout flexibility.
- Modified the fetchFileThumbnail function in ApiServerContext to include an optional parameter for handling 404 errors, improving error management during thumbnail fetching.
- Added a thumbnail property to the GCodeFile model, enabling thumbnail support for GCode files.
2026-08-01 15:11:02 +01:00
15 changed files with 534 additions and 280 deletions

View File

@ -34,6 +34,7 @@ import AuthCallback from './components/App/AuthCallback.jsx'
import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx' import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx'
import MarketplaceAuthCallback from './components/Dashboard/Sales/Marketplaces/MarketplaceAuthCallback.jsx' import MarketplaceAuthCallback from './components/Dashboard/Sales/Marketplaces/MarketplaceAuthCallback.jsx'
import AuthLaunch from './components/App/AppLaunch.jsx' import AuthLaunch from './components/App/AppLaunch.jsx'
import { TableStateProvider } from './components/Dashboard/context/TableStateContext.jsx'
const SlicerIntegration = lazy( const SlicerIntegration = lazy(
() => () =>
import('./components/Dashboard/Production/Printers/SlicerIntegration.jsx') import('./components/Dashboard/Production/Printers/SlicerIntegration.jsx')
@ -86,6 +87,7 @@ const AppContent = () => {
<NotificationProvider> <NotificationProvider>
<SpotlightProvider> <SpotlightProvider>
<ActionsModalProvider> <ActionsModalProvider>
<TableStateProvider>
<Routes> <Routes>
<Route <Route
path='/applaunch' path='/applaunch'
@ -156,6 +158,7 @@ const AppContent = () => {
} }
/> />
</Routes> </Routes>
</TableStateProvider>
</ActionsModalProvider> </ActionsModalProvider>
</SpotlightProvider> </SpotlightProvider>
</NotificationProvider> </NotificationProvider>

View File

@ -12,7 +12,6 @@ import DashboardBreadcrumb from './common/DashboardBreadcrumb'
import DeveloperSidebar from './Developer/DeveloperSidebar' import DeveloperSidebar from './Developer/DeveloperSidebar'
import { useThemeContext } from './context/ThemeContext' import { useThemeContext } from './context/ThemeContext'
import { MessageProvider } from './context/MessageContext' import { MessageProvider } from './context/MessageContext'
import { TableStateProvider } from './context/TableStateContext'
const { Content } = Layout const { Content } = Layout
@ -29,7 +28,6 @@ const DashboardLayout = ({ children }) => {
return ( return (
<MessageProvider> <MessageProvider>
<TableStateProvider>
<Layout <Layout
style={{ height: 'var(--unit-100vh)' }} style={{ height: 'var(--unit-100vh)' }}
className={isDarkMode ? 'dark-mode' : 'light-mode'} className={isDarkMode ? 'dark-mode' : 'light-mode'}
@ -64,7 +62,6 @@ const DashboardLayout = ({ children }) => {
</Layout> </Layout>
</Layout> </Layout>
</Layout> </Layout>
</TableStateProvider>
</MessageProvider> </MessageProvider>
) )
} }

View File

@ -33,6 +33,7 @@ import LoadFilamentStock from '../../Inventory/FilamentStocks/LoadFilamentStock.
import UnloadFilamentStock from '../../Inventory/FilamentStocks/UnloadFilamentStock.jsx' import UnloadFilamentStock from '../../Inventory/FilamentStocks/UnloadFilamentStock.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx' import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import PrinterControlButtons from '../../common/PrinterControlButtons.jsx'
const log = loglevel.getLogger('ControlPrinter') const log = loglevel.getLogger('ControlPrinter')
log.setLevel(config.logLevel) log.setLevel(config.logLevel)
@ -182,6 +183,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
> >
<PrinterTemperaturePanel <PrinterTemperaturePanel
id={printerId} id={printerId}
offline={!objectFormState.objectData?.online}
disabled={ disabled={
!objectFormState.objectData?.online || objectFormState.loading !objectFormState.objectData?.online || objectFormState.loading
} }
@ -243,6 +245,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
> >
<PrinterMiscPanel <PrinterMiscPanel
id={printerId} id={printerId}
offline={!objectFormState.objectData?.online}
disabled={ disabled={
!objectFormState.objectData?.online || objectFormState.loading !objectFormState.objectData?.online || objectFormState.loading
} }
@ -272,7 +275,8 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
visibleActions={{ visibleActions={{
edit: false, edit: false,
info: !slicerIntegration, info: !slicerIntegration,
control: !slicerIntegration control: !slicerIntegration,
newPrinterProfile: !slicerIntegration
}} }}
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
/> />
@ -318,11 +322,26 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
/> />
</Space> </Space>
</Space> </Space>
<Flex gap='small'>
<PrinterControlButtons
callAction={(action) =>
actionHandlerRef.current?.callAction(action)
}
objectData={objectFormState.objectData}
disabled={
!objectFormState.objectData?.online || objectFormState.loading
}
loading={objectFormState.loading}
/>
{!slicerIntegration && (
<ObjectTableNavigationButtons <ObjectTableNavigationButtons
showEndingDivider={true}
disabled={objectFormState.loading} disabled={objectFormState.loading}
_id={printerId} _id={printerId}
objectType='printer' objectType='printer'
/> />
)}
</Flex>
</Flex> </Flex>
<AlertsDisplay <AlertsDisplay

View File

@ -36,8 +36,6 @@ const FilePreview = ({ file, style = {} }) => {
} }
}, [file._id, file?.type, fetchPreview, token]) }, [file._id, file?.type, fetchPreview, token])
console.log(file)
if (loading == true || !file?.type) { if (loading == true || !file?.type) {
return <LoadingPlaceholder message={'Loading file preview...'} /> return <LoadingPlaceholder message={'Loading file preview...'} />
} }

View File

@ -1,6 +1,10 @@
import { useEffect, useRef, cloneElement, useMemo } from 'react' import { useEffect, useRef, cloneElement, useMemo } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Popover, Typography } from 'antd' import { Popover, Typography } from 'antd'
import loglevel from 'loglevel'
import config from '../../../config'
const logger = loglevel.getLogger('ApiServerContext')
logger.setLevel(config.logLevel)
const MODIFIER_ALIASES = { const MODIFIER_ALIASES = {
cmd: 'meta', cmd: 'meta',
@ -59,7 +63,9 @@ const KeyboardShortcut = ({
} }
if (log) { if (log) {
console.log('Key Down:', key, 'Pressed Keys:', [...pressedKeysRef.current]) logger.info('Key Down:', key, 'Pressed Keys:', [
...pressedKeysRef.current
])
} }
if ( if (

View File

@ -37,9 +37,48 @@ function filterActionsByVisibility(actions, visibleActions) {
}) })
} }
function cleanDividers(items) {
if (!Array.isArray(items) || items.length === 0) return []
const cleaned = []
for (const item of items) {
if (!item) continue
if (item.type === 'divider') {
if (cleaned.length === 0) continue
if (cleaned[cleaned.length - 1]?.type === 'divider') continue
cleaned.push(item)
continue
}
if (item.children && Array.isArray(item.children)) {
const children = cleanDividers(item.children)
if (children.length === 0) continue
cleaned.push({ ...item, children })
continue
}
cleaned.push(item)
}
if (cleaned[cleaned.length - 1]?.type === 'divider') {
cleaned.pop()
}
return cleaned
}
// Recursively map actions to AntD Dropdown items // Recursively map actions to AntD Dropdown items
function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) { function mapActionsToMenuItems(
return actions.map((action) => { actions,
currentUrlWithActions,
id,
objectData,
userProfile
) {
return cleanDividers(
actions.map((action) => {
if (action.type === 'divider') { if (action.type === 'divider') {
return { type: 'divider' } return { type: 'divider' }
} }
@ -48,8 +87,6 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
var disabled = actionUrl && actionUrl === currentUrlWithActions var disabled = actionUrl && actionUrl === currentUrlWithActions
var visible = true var visible = true
const { userProfile } = useContext(AuthContext)
if (action.disabled) { if (action.disabled) {
if (typeof action.disabled === 'function') { if (typeof action.disabled === 'function') {
disabled = action.disabled({ ...objectData, _user: userProfile }) disabled = action.disabled({ ...objectData, _user: userProfile })
@ -66,6 +103,10 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
} }
} }
if (visible != true) {
return null
}
const item = { const item = {
key: action.key || action.name, key: action.key || action.name,
label: action.label, label: action.label,
@ -78,13 +119,13 @@ function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
action.children, action.children,
currentUrlWithActions, currentUrlWithActions,
id, id,
objectData objectData,
userProfile
) )
} }
if (visible == true) {
return item return item
}
}) })
)
} }
const stripActionParam = (pathname, search) => { const stripActionParam = (pathname, search) => {
@ -108,6 +149,7 @@ const ObjectActions = ({
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const { showActionsModal } = useActionsModal() const { showActionsModal } = useActionsModal()
const { userProfile } = useContext(AuthContext)
// Get current url without 'action' param // Get current url without 'action' param
const currentUrlWithoutActions = stripActionParam( const currentUrlWithoutActions = stripActionParam(
location.pathname, location.pathname,
@ -120,11 +162,14 @@ const ObjectActions = ({
visibleActions visibleActions
) )
const filteredActions = visibilityFilteredActions.filter( const filteredActions = cleanDividers(
visibilityFilteredActions.filter(
(action) => (action) =>
action.type === 'divider' ||
typeof action.url !== 'function' || typeof action.url !== 'function' ||
action.url(id) !== currentUrlWithoutActions action.url(id) !== currentUrlWithoutActions
) )
)
const currentUrlWithActions = location.pathname + location.search const currentUrlWithActions = location.pathname + location.search
@ -134,7 +179,8 @@ const ObjectActions = ({
filteredActions, filteredActions,
currentUrlWithActions, currentUrlWithActions,
id, id,
objectData objectData,
userProfile
), ),
onClick: (info) => { onClick: (info) => {
// Find the action by key // Find the action by key

View File

@ -127,8 +127,6 @@ const ObjectForm = forwardRef(
searchParams.delete(actionParam) searchParams.delete(actionParam)
const newSearch = searchParams.toString() const newSearch = searchParams.toString()
console.log('newSearch', newSearch)
console.log('location.pathname', location.pathname)
const newPath = location.pathname + (newSearch ? `?${newSearch}` : '') const newPath = location.pathname + (newSearch ? `?${newSearch}` : '')
navigate(newPath, { replace: true }) navigate(newPath, { replace: true })
}, },
@ -537,7 +535,7 @@ const ObjectForm = forwardRef(
} }
const activityUnsubscribe = subscribeToObjectActivity( const activityUnsubscribe = subscribeToObjectActivity(
id, id?.toLowerCase(),
type, type,
activityHandler activityHandler
) )
@ -565,7 +563,7 @@ const ObjectForm = forwardRef(
} }
const objectUpdatesUnsubscribe = subscribeToObjectUpdates( const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
id, id?.toLowerCase(),
type, type,
objectUpdateHandler objectUpdateHandler
) )

View File

@ -693,7 +693,7 @@ const ObjectTable = forwardRef(
// Subscribe to new items only // Subscribe to new items only
newItemIds.forEach((itemId) => { newItemIds.forEach((itemId) => {
const unsubscribe = subscribeToObjectUpdates( const unsubscribe = subscribeToObjectUpdates(
itemId, itemId?.toLowerCase(),
type, type,
(updateData) => { (updateData) => {
updateEventHandlerRef.current(itemId, updateData) updateEventHandlerRef.current(itemId, updateData)

View File

@ -0,0 +1,89 @@
import { createElement, useContext } from 'react'
import { Button, Space } from 'antd'
import PropTypes from 'prop-types'
import { getModelByName } from '../../../database/ObjectModels'
import { AuthContext } from '../context/AuthContext'
import PlayCircleIcon from '../../Icons/PlayCircleIcon'
import PauseCircleIcon from '../../Icons/PauseCircleIcon'
import StopCircleIcon from '../../Icons/StopCircleIcon'
const findAction = (actions, name) => {
for (const action of actions) {
if (action.name === name) return action
if (action.children) {
const found = findAction(action.children, name)
if (found) return found
}
}
return null
}
const isActionDisabled = (action, objectData, userProfile, disabled) => {
if (disabled) return true
if (!action?.disabled) return false
if (typeof action.disabled === 'function') {
return action.disabled({ ...objectData, _user: userProfile })
}
return action.disabled
}
const PrinterControlButtons = ({ callAction, objectData, disabled }) => {
const { userProfile } = useContext(AuthContext)
const model = getModelByName('printer')
const startQueueAction = findAction(model.actions, 'startQueue')
const pauseJobAction = findAction(model.actions, 'pauseJob')
const resumeJobAction = findAction(model.actions, 'resumeJob')
const cancelJobAction = findAction(model.actions, 'cancelJob')
const isPaused = objectData?.state?.type === 'paused'
const startAction = isPaused ? resumeJobAction : startQueueAction
const startActionName = isPaused ? 'resumeJob' : 'startQueue'
return (
<Space size='small'>
<Button
icon={createElement(PlayCircleIcon)}
onClick={() => callAction(startActionName)}
disabled={isActionDisabled(
startAction,
objectData,
userProfile,
disabled
)}
title={startAction?.label}
/>
<Button
icon={createElement(PauseCircleIcon)}
onClick={() => callAction('pauseJob')}
disabled={isActionDisabled(
pauseJobAction,
objectData,
userProfile,
disabled
)}
title={pauseJobAction?.label}
/>
<Button
icon={createElement(StopCircleIcon)}
onClick={() => callAction('cancelJob')}
disabled={isActionDisabled(
cancelJobAction,
objectData,
userProfile,
disabled
)}
title={cancelJobAction?.label}
danger
/>
</Space>
)
}
PrinterControlButtons.propTypes = {
callAction: PropTypes.func.isRequired,
objectData: PropTypes.object.isRequired,
disabled: PropTypes.bool,
loading: PropTypes.bool
}
export default PrinterControlButtons

View File

@ -7,7 +7,12 @@ import merge from 'lodash/merge'
const { Text } = Typography const { Text } = Typography
const PrinterMiscPanel = ({ id, showControls = true, disabled = false }) => { const PrinterMiscPanel = ({
id,
showControls = true,
disabled = false,
offline = true
}) => {
const { subscribeToObjectEvent, connected, sendObjectAction } = const { subscribeToObjectEvent, connected, sendObjectAction } =
useContext(ApiServerContext) useContext(ApiServerContext)
const controlsDisabled = disabled || !connected const controlsDisabled = disabled || !connected
@ -35,10 +40,22 @@ const PrinterMiscPanel = ({ id, showControls = true, disabled = false }) => {
const [lcdBrightness, setLcdBrightness] = useState(0) const [lcdBrightness, setLcdBrightness] = useState(0)
const [beeperValue, setBeeperValue] = useState(0) const [beeperValue, setBeeperValue] = useState(0)
useEffect(() => {
if (offline) {
setMiscData({
fan: { speed: 0, target: 0 },
lcdBacklight: { brightness: 0 },
beeper: { value: 0 },
filamentSensor: { enabled: false, filamentDetected: false },
coolingFan: { speed: 0 }
})
}
}, [offline])
useEffect(() => { useEffect(() => {
if (id && connected == true) { if (id && connected == true) {
const miscEventUnsubscribe = subscribeToObjectEvent( const miscEventUnsubscribe = subscribeToObjectEvent(
id, id?.toLowerCase(),
'printer', 'printer',
'misc', 'misc',
(event) => { (event) => {
@ -193,7 +210,8 @@ const PrinterMiscPanel = ({ id, showControls = true, disabled = false }) => {
PrinterMiscPanel.propTypes = { PrinterMiscPanel.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
showControls: PropTypes.bool, showControls: PropTypes.bool,
disabled: PropTypes.bool disabled: PropTypes.bool,
offline: PropTypes.bool
} }
export default PrinterMiscPanel export default PrinterMiscPanel

View File

@ -30,11 +30,23 @@ const CustomCollapse = styled(Collapse)`
} }
` `
const controlLabelStyle = {
whiteSpace: 'nowrap',
flexShrink: 0,
minWidth: '120px'
}
const controlInputStyle = {
flex: 1,
minWidth: 0
}
const PrinterPositionPanel = ({ const PrinterPositionPanel = ({
id, id,
showControls = true, showControls = true,
showMoreInfo = true, showMoreInfo = true,
disabled = false disabled = false,
offline = true
}) => { }) => {
const { subscribeToObjectEvent, connected, sendObjectAction } = const { subscribeToObjectEvent, connected, sendObjectAction } =
useContext(ApiServerContext) useContext(ApiServerContext)
@ -55,10 +67,30 @@ const PrinterPositionPanel = ({
minCruiseRatio: 0 minCruiseRatio: 0
}) })
useEffect(() => {
if (offline) {
setPositionData({
speedFactor: 1.0,
speed: 100,
extrudeFactor: 1.0,
absoluteCoordinates: true,
absoluteExtrude: false,
homingOrigin: [0.0, 0.0, 0.0, 0.0],
toolheadPosition: [0.0, 0.0, 0.0, 0.0],
gcodePosition: [0.0, 0.0, 0.0, 0.0],
livePosition: [0.0, 0.0, 0.0, 0.0],
maxVelocity: 1000,
maxAcceleration: 1000,
squareCornerVelocity: 100,
minCruiseRatio: 0
})
}
}, [offline])
useEffect(() => { useEffect(() => {
if (id && connected == true) { if (id && connected == true) {
const motionEventUnsubscribe = subscribeToObjectEvent( const motionEventUnsubscribe = subscribeToObjectEvent(
id, id?.toLowerCase(),
'printer', 'printer',
'motion', 'motion',
(event) => { (event) => {
@ -73,6 +105,7 @@ const PrinterPositionPanel = ({
} }
} }
}, [id, connected, subscribeToObjectEvent]) }, [id, connected, subscribeToObjectEvent])
const [speedFactor, setSpeedFactor] = useState(positionData.speedFactor) const [speedFactor, setSpeedFactor] = useState(positionData.speedFactor)
const [extrudeFactor, setExtrudeFactor] = useState(positionData.extrudeFactor) const [extrudeFactor, setExtrudeFactor] = useState(positionData.extrudeFactor)
const [maxVelocity, setMaxVelocity] = useState(positionData.maxVelocity) const [maxVelocity, setMaxVelocity] = useState(positionData.maxVelocity)
@ -212,16 +245,22 @@ const PrinterPositionPanel = ({
</Descriptions> </Descriptions>
{showControls && ( {showControls && (
<> <>
<Space direction='vertical' style={{ width: '100%' }}> <Flex vertical gap='small' style={{ width: '100%' }}>
<Space direction='horizontal'> <Flex align='center' style={{ width: '100%' }}>
<Text>Max Velocity:</Text> <Text style={{ ...controlLabelStyle, minWidth: '91px' }}>
<Space.Compact block size='small'> Max Velocity:
</Text>
<Space.Compact
block
size='small'
style={{ flex: 1, minWidth: 0 }}
>
<InputNumber <InputNumber
value={round(maxVelocity, 2)} value={round(maxVelocity, 2)}
min={1} min={1}
max={10000} max={10000}
step={5} step={5}
style={{ width: '125px' }} style={controlInputStyle}
suffix='mm/s' suffix='mm/s'
onChange={(value) => setMaxVelocity(value)} onChange={(value) => setMaxVelocity(value)}
onPressEnter={handleSetMaxVelocity} onPressEnter={handleSetMaxVelocity}
@ -237,18 +276,23 @@ const PrinterPositionPanel = ({
Set Set
</Button> </Button>
</Space.Compact> </Space.Compact>
</Space> </Flex>
<Space direction='vertical' style={{ width: '100%' }}> <Flex align='center' style={{ width: '100%' }}>
<Space direction='horizontal'> <Text style={{ ...controlLabelStyle, minWidth: '120px' }}>
<Text>Max Acceleration:</Text> Max Acceleration:
<Space.Compact block size='small'> </Text>
<Space.Compact
block
size='small'
style={{ flex: 1, minWidth: 0 }}
>
<InputNumber <InputNumber
value={round(maxAcceleration, 2)} value={round(maxAcceleration, 2)}
min={1} min={1}
max={10000} max={10000}
step={5} step={5}
style={{ width: '125px' }} style={controlInputStyle}
suffix='mm/s²' suffix='mm/s²'
onChange={(value) => setMaxAcceleration(value)} onChange={(value) => setMaxAcceleration(value)}
onPressEnter={handleSetMaxAcceleration} onPressEnter={handleSetMaxAcceleration}
@ -264,20 +308,24 @@ const PrinterPositionPanel = ({
Set Set
</Button> </Button>
</Space.Compact> </Space.Compact>
</Space> </Flex>
</Space>
<Space direction='vertical' style={{ width: '100%' }}> <Flex align='center' style={{ width: '100%' }}>
<Space direction='horizontal'> <Text style={{ ...controlLabelStyle, minWidth: '102px' }}>
<Text>Sqr Corner Vel:</Text> Sqr Corner Vel:
<Space.Compact block size='small'> </Text>
<Space.Compact
block
size='small'
style={{ flex: 1, minWidth: 0 }}
>
<InputNumber <InputNumber
value={round(squareCornerVelocity, 2) || 0} value={round(squareCornerVelocity, 2) || 0}
min={0.1} min={0.1}
max={1000} max={1000}
step={0.1} step={0.1}
style={controlInputStyle}
suffix='mm/s' suffix='mm/s'
style={{ width: '125px' }}
onChange={(value) => setSquareCornerVelocity(value)} onChange={(value) => setSquareCornerVelocity(value)}
onPressEnter={handleSetSquareCornerVelocity} onPressEnter={handleSetSquareCornerVelocity}
size='small' size='small'
@ -292,20 +340,24 @@ const PrinterPositionPanel = ({
Set Set
</Button> </Button>
</Space.Compact> </Space.Compact>
</Space> </Flex>
</Space>
<Space direction='vertical' style={{ width: '100%' }}> <Flex align='center' style={{ width: '100%' }}>
<Space direction='horizontal'> <Text style={{ ...controlLabelStyle, minWidth: '114px' }}>
<Text>Min Cruise Ratio:</Text> Min Cruise Ratio:
<Space.Compact block size='small'> </Text>
<Space.Compact
block
size='small'
style={{ flex: 1, minWidth: 0 }}
>
<InputNumber <InputNumber
value={round(minCruiseRatio * 100, 2) || 0} value={round(minCruiseRatio * 100, 2) || 0}
min={0} min={0}
max={100} max={100}
step={1} step={1}
suffix='%' suffix='%'
style={{ width: '125px' }} style={controlInputStyle}
onChange={(value) => setMinCruiseRatio(value / 100)} onChange={(value) => setMinCruiseRatio(value / 100)}
onPressEnter={handleSetMinCruiseRatio} onPressEnter={handleSetMinCruiseRatio}
size='small' size='small'
@ -320,9 +372,8 @@ const PrinterPositionPanel = ({
Set Set
</Button> </Button>
</Space.Compact> </Space.Compact>
</Space> </Flex>
</Space> </Flex>
</Space>
</> </>
)} )}
<Text>Homing Origin:</Text> <Text>Homing Origin:</Text>
@ -398,16 +449,22 @@ const PrinterPositionPanel = ({
</Descriptions> </Descriptions>
{showControls && ( {showControls && (
<> <>
<Space direction='vertical' style={{ width: '100%' }}> <Flex vertical gap='small' style={{ width: '100%' }}>
<Space direction='horizontal'> <Flex align='center' style={{ width: '100%' }}>
<Text>Speed Factor:</Text> <Text style={{ ...controlLabelStyle, minWidth: '96px' }}>
<Space.Compact block size='small'> Speed Factor:
</Text>
<Space.Compact
block
size='small'
style={{ flex: 1, minWidth: 0 }}
>
<InputNumber <InputNumber
value={round(speedFactor, 2)} value={round(speedFactor, 2)}
min={0.1} min={0.1}
max={2} max={2}
step={0.1} step={0.1}
style={{ width: '125px' }} style={controlInputStyle}
suffix='%' suffix='%'
onChange={(value) => setSpeedFactor(value)} onChange={(value) => setSpeedFactor(value)}
onPressEnter={handleSetSpeedFactor} onPressEnter={handleSetSpeedFactor}
@ -423,17 +480,23 @@ const PrinterPositionPanel = ({
Set Set
</Button> </Button>
</Space.Compact> </Space.Compact>
</Space> </Flex>
<Space direction='horizontal'> <Flex align='center' style={{ width: '100%' }}>
<Text>Extrude Factor:</Text> <Text style={{ ...controlLabelStyle, minWidth: '105px' }}>
<Space.Compact block size='small'> Extrude Factor:
</Text>
<Space.Compact
block
size='small'
style={{ flex: 1, minWidth: 0 }}
>
<InputNumber <InputNumber
value={round(extrudeFactor, 2)} value={round(extrudeFactor, 2)}
min={0.1} min={0.1}
max={2} max={2}
step={0.1} step={0.1}
style={{ width: '125px' }} style={controlInputStyle}
suffix='%' suffix='%'
onChange={(value) => setExtrudeFactor(value)} onChange={(value) => setExtrudeFactor(value)}
onPressEnter={handleSetExtrudeFactor} onPressEnter={handleSetExtrudeFactor}
@ -449,8 +512,8 @@ const PrinterPositionPanel = ({
Set Set
</Button> </Button>
</Space.Compact> </Space.Compact>
</Space> </Flex>
</Space> </Flex>
</> </>
)} )}
<Descriptions <Descriptions
@ -509,7 +572,8 @@ PrinterPositionPanel.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
showControls: PropTypes.bool, showControls: PropTypes.bool,
showMoreInfo: PropTypes.bool, showMoreInfo: PropTypes.bool,
disabled: PropTypes.bool disabled: PropTypes.bool,
offline: PropTypes.bool
} }
export default PrinterPositionPanel export default PrinterPositionPanel

View File

@ -38,7 +38,8 @@ const PrinterTemperaturePanel = ({
showExtruder = true, showExtruder = true,
showBed = true, showBed = true,
showMoreInfo = true, showMoreInfo = true,
disabled = false disabled = false,
offline = true
}) => { }) => {
const [temperatureData, setTemperatureData] = useState({ const [temperatureData, setTemperatureData] = useState({
extruder: { extruder: {
@ -55,6 +56,17 @@ const PrinterTemperaturePanel = ({
ambiant: 0 ambiant: 0
}) })
useEffect(() => {
if (offline) {
setTemperatureData({
extruder: { current: 0, target: 0, power: 0 },
bed: { current: 0, target: 0, power: 0 },
pinda: 0,
ambiant: 0
})
}
}, [offline])
const { subscribeToObjectEvent, connected, sendObjectAction } = const { subscribeToObjectEvent, connected, sendObjectAction } =
useContext(ApiServerContext) useContext(ApiServerContext)
const controlsDisabled = disabled || !connected const controlsDisabled = disabled || !connected
@ -72,9 +84,9 @@ const PrinterTemperaturePanel = ({
}, [temperatureData.bed?.target]) }, [temperatureData.bed?.target])
useEffect(() => { useEffect(() => {
if (id && connected == true) { if (id && connected == true && offline == false) {
const temperatureEventUnsubscribe = subscribeToObjectEvent( const temperatureEventUnsubscribe = subscribeToObjectEvent(
id, id?.toLowerCase(),
'printer', 'printer',
'temperature', 'temperature',
(event) => { (event) => {
@ -88,7 +100,7 @@ const PrinterTemperaturePanel = ({
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe() if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
} }
} }
}, [id, connected, subscribeToObjectEvent]) }, [id, connected, subscribeToObjectEvent, offline])
const [extruderTarget, setExtruderTarget] = useState(0) const [extruderTarget, setExtruderTarget] = useState(0)
const [bedTarget, setBedTarget] = useState(0) const [bedTarget, setBedTarget] = useState(0)
@ -177,7 +189,7 @@ const PrinterTemperaturePanel = ({
value={extruderTarget} value={extruderTarget}
min={0} min={0}
max={300} max={300}
style={{ width: '120px' }} style={{ width: '140px' }}
addonAfter='°C' addonAfter='°C'
onChange={(value) => setExtruderTarget(value || 0)} onChange={(value) => setExtruderTarget(value || 0)}
onPressEnter={() => onPressEnter={() =>
@ -241,7 +253,7 @@ const PrinterTemperaturePanel = ({
value={bedTarget} value={bedTarget}
min={0} min={0}
max={300} max={300}
style={{ width: '120px' }} style={{ width: '140px' }}
addonAfter='°C' addonAfter='°C'
onChange={(value) => setBedTarget(value || 0)} onChange={(value) => setBedTarget(value || 0)}
onPressEnter={() => onPressEnter={() =>
@ -309,7 +321,8 @@ PrinterTemperaturePanel.propTypes = {
showBed: PropTypes.bool, showBed: PropTypes.bool,
showMoreInfo: PropTypes.bool, showMoreInfo: PropTypes.bool,
shouldUnsubscribe: PropTypes.bool, shouldUnsubscribe: PropTypes.bool,
disabled: PropTypes.bool disabled: PropTypes.bool,
offline: PropTypes.bool
} }
export default PrinterTemperaturePanel export default PrinterTemperaturePanel

View File

@ -42,7 +42,6 @@ const Thumbnail = function Thumbnail({
borderRadius: '5px', borderRadius: '5px',
width: size, width: size,
height: size, height: size,
border: 'none',
...style ...style
} }
@ -151,7 +150,7 @@ const Thumbnail = function Thumbnail({
<Card <Card
className={className} className={className}
style={cardStyle} style={cardStyle}
styles={{ body: { padding: 0, height: '100%' } }} styles={{ body: { padding: 0, height: '100%', width: style?.width } }}
> >
<Flex justify='center' align='center' style={{ height: '100%' }}> <Flex justify='center' align='center' style={{ height: '100%' }}>
{fallback || ( {fallback || (

View File

@ -1510,7 +1510,7 @@ const ApiServerProvider = ({ children }) => {
} }
} }
const fetchFileThumbnail = async (file, size) => { const fetchFileThumbnail = async (file, size, show404Error = false) => {
try { try {
const response = await axios.get( const response = await axios.get(
`${config.backendUrl}/files/${file._id}/thumbnail`, `${config.backendUrl}/files/${file._id}/thumbnail`,
@ -1528,6 +1528,9 @@ const ApiServerProvider = ({ children }) => {
}) })
return window.URL.createObjectURL(blob) return window.URL.createObjectURL(blob)
} catch (err) { } catch (err) {
if (err.response.status === 404 && show404Error == false) {
return null
}
console.error(err) console.error(err)
showError(err, () => { showError(err, () => {
fetchFileThumbnail(file, size) fetchFileThumbnail(file, size)

View File

@ -164,6 +164,7 @@ export const GCodeFile = {
showPreview: false, showPreview: false,
showHyperlink: true, showHyperlink: true,
masterFilter: ['.gcode', '.g'], masterFilter: ['.gcode', '.g'],
thumbnail: true,
columnWidth: 200 columnWidth: 200
}, },
{ {