Compare commits
6 Commits
6ea5f2848a
...
d982b8dfb3
| Author | SHA1 | Date | |
|---|---|---|---|
| d982b8dfb3 | |||
| a2d27c692a | |||
| e3544ef2e2 | |||
| e52798d4ac | |||
| f3d6bebc38 | |||
| 12cae32e33 |
141
src/App.jsx
141
src/App.jsx
@ -34,6 +34,7 @@ import AuthCallback from './components/App/AuthCallback.jsx'
|
||||
import EmailNotificationTemplate from './components/Email/EmailNotificationTemplate.jsx'
|
||||
import MarketplaceAuthCallback from './components/Dashboard/Sales/Marketplaces/MarketplaceAuthCallback.jsx'
|
||||
import AuthLaunch from './components/App/AppLaunch.jsx'
|
||||
import { TableStateProvider } from './components/Dashboard/context/TableStateContext.jsx'
|
||||
const SlicerIntegration = lazy(
|
||||
() =>
|
||||
import('./components/Dashboard/Production/Printers/SlicerIntegration.jsx')
|
||||
@ -86,76 +87,78 @@ const AppContent = () => {
|
||||
<NotificationProvider>
|
||||
<SpotlightProvider>
|
||||
<ActionsModalProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path='/applaunch'
|
||||
element={<AuthLaunch />}
|
||||
/>
|
||||
<Route
|
||||
path='/dashboard/electron/spotlightcontent'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<ElectronSpotlightContentPage />
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<Navigate
|
||||
to='/dashboard/production/overview'
|
||||
replace
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/callback'
|
||||
element={<AuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/marketplace/callback'
|
||||
element={<MarketplaceAuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/email/notification'
|
||||
element={<EmailNotificationTemplate />}
|
||||
/>
|
||||
<Route
|
||||
path='/slicer'
|
||||
element={<SlicerIntegration />}
|
||||
/>
|
||||
<TableStateProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
path='/applaunch'
|
||||
element={<AuthLaunch />}
|
||||
/>
|
||||
<Route
|
||||
path='/dashboard/electron/spotlightcontent'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<ElectronSpotlightContentPage />
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => (
|
||||
<Navigate
|
||||
to='/dashboard/production/overview'
|
||||
replace
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/callback'
|
||||
element={<AuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/auth/marketplace/callback'
|
||||
element={<MarketplaceAuthCallback />}
|
||||
/>
|
||||
<Route
|
||||
path='/email/notification'
|
||||
element={<EmailNotificationTemplate />}
|
||||
/>
|
||||
<Route
|
||||
path='/slicer'
|
||||
element={<SlicerIntegration />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path='/dashboard'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => <Dashboard />}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ProductionRoutes}
|
||||
{InventoryRoutes}
|
||||
{FinanceRoutes}
|
||||
{SalesRoutes}
|
||||
{ManagementRoutes}
|
||||
{DeveloperRoutes}
|
||||
</Route>
|
||||
<Route
|
||||
path='*'
|
||||
element={
|
||||
<AppError
|
||||
message='Error 404. Page not found.'
|
||||
showRefresh={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
<Route
|
||||
path='/dashboard'
|
||||
element={
|
||||
<PrivateRoute
|
||||
component={() => <Dashboard />}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{ProductionRoutes}
|
||||
{InventoryRoutes}
|
||||
{FinanceRoutes}
|
||||
{SalesRoutes}
|
||||
{ManagementRoutes}
|
||||
{DeveloperRoutes}
|
||||
</Route>
|
||||
<Route
|
||||
path='*'
|
||||
element={
|
||||
<AppError
|
||||
message='Error 404. Page not found.'
|
||||
showRefresh={false}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</TableStateProvider>
|
||||
</ActionsModalProvider>
|
||||
</SpotlightProvider>
|
||||
</NotificationProvider>
|
||||
|
||||
@ -12,7 +12,6 @@ import DashboardBreadcrumb from './common/DashboardBreadcrumb'
|
||||
import DeveloperSidebar from './Developer/DeveloperSidebar'
|
||||
import { useThemeContext } from './context/ThemeContext'
|
||||
import { MessageProvider } from './context/MessageContext'
|
||||
import { TableStateProvider } from './context/TableStateContext'
|
||||
|
||||
const { Content } = Layout
|
||||
|
||||
@ -29,42 +28,40 @@ const DashboardLayout = ({ children }) => {
|
||||
|
||||
return (
|
||||
<MessageProvider>
|
||||
<TableStateProvider>
|
||||
<Layout
|
||||
style={{ height: 'var(--unit-100vh)' }}
|
||||
className={isDarkMode ? 'dark-mode' : 'light-mode'}
|
||||
>
|
||||
<DashboardNavigation />
|
||||
<Layout>
|
||||
{isProduction ? (
|
||||
<ProductionSidebar />
|
||||
) : isInventory ? (
|
||||
<InventorySidebar />
|
||||
) : isFinance ? (
|
||||
<FinanceSidebar />
|
||||
) : isSales ? (
|
||||
<SalesSidebar />
|
||||
) : isManagement ? (
|
||||
<ManagementSidebar />
|
||||
) : isDeveloper ? (
|
||||
<DeveloperSidebar />
|
||||
) : (
|
||||
<ProductionSidebar /> // Default to production sidebar
|
||||
)}
|
||||
<Layout style={{ padding: '24px' }}>
|
||||
<Content>
|
||||
<Flex vertical style={{ height: '100%' }} gap='20px'>
|
||||
<Flex justify='space-between'>
|
||||
<DashboardBreadcrumb style={{ margin: '16px 0' }} />
|
||||
</Flex>
|
||||
|
||||
{children}
|
||||
<Layout
|
||||
style={{ height: 'var(--unit-100vh)' }}
|
||||
className={isDarkMode ? 'dark-mode' : 'light-mode'}
|
||||
>
|
||||
<DashboardNavigation />
|
||||
<Layout>
|
||||
{isProduction ? (
|
||||
<ProductionSidebar />
|
||||
) : isInventory ? (
|
||||
<InventorySidebar />
|
||||
) : isFinance ? (
|
||||
<FinanceSidebar />
|
||||
) : isSales ? (
|
||||
<SalesSidebar />
|
||||
) : isManagement ? (
|
||||
<ManagementSidebar />
|
||||
) : isDeveloper ? (
|
||||
<DeveloperSidebar />
|
||||
) : (
|
||||
<ProductionSidebar /> // Default to production sidebar
|
||||
)}
|
||||
<Layout style={{ padding: '24px' }}>
|
||||
<Content>
|
||||
<Flex vertical style={{ height: '100%' }} gap='20px'>
|
||||
<Flex justify='space-between'>
|
||||
<DashboardBreadcrumb style={{ margin: '16px 0' }} />
|
||||
</Flex>
|
||||
</Content>
|
||||
</Layout>
|
||||
|
||||
{children}
|
||||
</Flex>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</TableStateProvider>
|
||||
</Layout>
|
||||
</MessageProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ import LoadFilamentStock from '../../Inventory/FilamentStocks/LoadFilamentStock.
|
||||
import UnloadFilamentStock from '../../Inventory/FilamentStocks/UnloadFilamentStock.jsx'
|
||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||
import PrinterControlButtons from '../../common/PrinterControlButtons.jsx'
|
||||
|
||||
const log = loglevel.getLogger('ControlPrinter')
|
||||
log.setLevel(config.logLevel)
|
||||
@ -182,6 +183,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
|
||||
>
|
||||
<PrinterTemperaturePanel
|
||||
id={printerId}
|
||||
offline={!objectFormState.objectData?.online}
|
||||
disabled={
|
||||
!objectFormState.objectData?.online || objectFormState.loading
|
||||
}
|
||||
@ -243,6 +245,7 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
|
||||
>
|
||||
<PrinterMiscPanel
|
||||
id={printerId}
|
||||
offline={!objectFormState.objectData?.online}
|
||||
disabled={
|
||||
!objectFormState.objectData?.online || objectFormState.loading
|
||||
}
|
||||
@ -272,7 +275,8 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
|
||||
visibleActions={{
|
||||
edit: false,
|
||||
info: !slicerIntegration,
|
||||
control: !slicerIntegration
|
||||
control: !slicerIntegration,
|
||||
newPrinterProfile: !slicerIntegration
|
||||
}}
|
||||
objectData={objectFormState.objectData}
|
||||
/>
|
||||
@ -318,11 +322,26 @@ const ControlPrinter = ({ slicerIntegration = false }) => {
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
<ObjectTableNavigationButtons
|
||||
disabled={objectFormState.loading}
|
||||
_id={printerId}
|
||||
objectType='printer'
|
||||
/>
|
||||
<Flex gap='small'>
|
||||
<PrinterControlButtons
|
||||
callAction={(action) =>
|
||||
actionHandlerRef.current?.callAction(action)
|
||||
}
|
||||
objectData={objectFormState.objectData}
|
||||
disabled={
|
||||
!objectFormState.objectData?.online || objectFormState.loading
|
||||
}
|
||||
loading={objectFormState.loading}
|
||||
/>
|
||||
{!slicerIntegration && (
|
||||
<ObjectTableNavigationButtons
|
||||
showEndingDivider={true}
|
||||
disabled={objectFormState.loading}
|
||||
_id={printerId}
|
||||
objectType='printer'
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<AlertsDisplay
|
||||
|
||||
@ -36,8 +36,6 @@ const FilePreview = ({ file, style = {} }) => {
|
||||
}
|
||||
}, [file._id, file?.type, fetchPreview, token])
|
||||
|
||||
console.log(file)
|
||||
|
||||
if (loading == true || !file?.type) {
|
||||
return <LoadingPlaceholder message={'Loading file preview...'} />
|
||||
}
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { useEffect, useRef, cloneElement, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
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 = {
|
||||
cmd: 'meta',
|
||||
@ -59,7 +63,9 @@ const KeyboardShortcut = ({
|
||||
}
|
||||
|
||||
if (log) {
|
||||
console.log('Key Down:', key, 'Pressed Keys:', [...pressedKeysRef.current])
|
||||
logger.info('Key Down:', key, 'Pressed Keys:', [
|
||||
...pressedKeysRef.current
|
||||
])
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@ -37,54 +37,95 @@ 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
|
||||
function mapActionsToMenuItems(actions, currentUrlWithActions, id, objectData) {
|
||||
return actions.map((action) => {
|
||||
if (action.type === 'divider') {
|
||||
return { type: 'divider' }
|
||||
}
|
||||
const actionUrl = action.url ? action.url(id) : undefined
|
||||
|
||||
var disabled = actionUrl && actionUrl === currentUrlWithActions
|
||||
var visible = true
|
||||
|
||||
const { userProfile } = useContext(AuthContext)
|
||||
|
||||
if (action.disabled) {
|
||||
if (typeof action.disabled === 'function') {
|
||||
disabled = action.disabled({ ...objectData, _user: userProfile })
|
||||
} else {
|
||||
disabled = action.disabled
|
||||
function mapActionsToMenuItems(
|
||||
actions,
|
||||
currentUrlWithActions,
|
||||
id,
|
||||
objectData,
|
||||
userProfile
|
||||
) {
|
||||
return cleanDividers(
|
||||
actions.map((action) => {
|
||||
if (action.type === 'divider') {
|
||||
return { type: 'divider' }
|
||||
}
|
||||
}
|
||||
const actionUrl = action.url ? action.url(id) : undefined
|
||||
|
||||
if (action.visible) {
|
||||
if (typeof action.visible === 'function') {
|
||||
visible = action.visible(objectData)
|
||||
} else {
|
||||
visible = action.visible
|
||||
var disabled = actionUrl && actionUrl === currentUrlWithActions
|
||||
var visible = true
|
||||
|
||||
if (action.disabled) {
|
||||
if (typeof action.disabled === 'function') {
|
||||
disabled = action.disabled({ ...objectData, _user: userProfile })
|
||||
} else {
|
||||
disabled = action.disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const item = {
|
||||
key: action.key || action.name,
|
||||
label: action.label,
|
||||
danger: action?.danger || false,
|
||||
icon: action.icon ? createElement(action.icon) : undefined,
|
||||
disabled
|
||||
}
|
||||
if (action.children && Array.isArray(action.children)) {
|
||||
item.children = mapActionsToMenuItems(
|
||||
action.children,
|
||||
currentUrlWithActions,
|
||||
id,
|
||||
objectData
|
||||
)
|
||||
}
|
||||
if (visible == true) {
|
||||
if (action.visible) {
|
||||
if (typeof action.visible === 'function') {
|
||||
visible = action.visible(objectData)
|
||||
} else {
|
||||
visible = action.visible
|
||||
}
|
||||
}
|
||||
|
||||
if (visible != true) {
|
||||
return null
|
||||
}
|
||||
|
||||
const item = {
|
||||
key: action.key || action.name,
|
||||
label: action.label,
|
||||
danger: action?.danger || false,
|
||||
icon: action.icon ? createElement(action.icon) : undefined,
|
||||
disabled
|
||||
}
|
||||
if (action.children && Array.isArray(action.children)) {
|
||||
item.children = mapActionsToMenuItems(
|
||||
action.children,
|
||||
currentUrlWithActions,
|
||||
id,
|
||||
objectData,
|
||||
userProfile
|
||||
)
|
||||
}
|
||||
return item
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const stripActionParam = (pathname, search) => {
|
||||
@ -108,6 +149,7 @@ const ObjectActions = ({
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { showActionsModal } = useActionsModal()
|
||||
const { userProfile } = useContext(AuthContext)
|
||||
// Get current url without 'action' param
|
||||
const currentUrlWithoutActions = stripActionParam(
|
||||
location.pathname,
|
||||
@ -120,10 +162,13 @@ const ObjectActions = ({
|
||||
visibleActions
|
||||
)
|
||||
|
||||
const filteredActions = visibilityFilteredActions.filter(
|
||||
(action) =>
|
||||
typeof action.url !== 'function' ||
|
||||
action.url(id) !== currentUrlWithoutActions
|
||||
const filteredActions = cleanDividers(
|
||||
visibilityFilteredActions.filter(
|
||||
(action) =>
|
||||
action.type === 'divider' ||
|
||||
typeof action.url !== 'function' ||
|
||||
action.url(id) !== currentUrlWithoutActions
|
||||
)
|
||||
)
|
||||
|
||||
const currentUrlWithActions = location.pathname + location.search
|
||||
@ -134,7 +179,8 @@ const ObjectActions = ({
|
||||
filteredActions,
|
||||
currentUrlWithActions,
|
||||
id,
|
||||
objectData
|
||||
objectData,
|
||||
userProfile
|
||||
),
|
||||
onClick: (info) => {
|
||||
// Find the action by key
|
||||
|
||||
@ -127,8 +127,6 @@ const ObjectForm = forwardRef(
|
||||
searchParams.delete(actionParam)
|
||||
const newSearch = searchParams.toString()
|
||||
|
||||
console.log('newSearch', newSearch)
|
||||
console.log('location.pathname', location.pathname)
|
||||
const newPath = location.pathname + (newSearch ? `?${newSearch}` : '')
|
||||
navigate(newPath, { replace: true })
|
||||
},
|
||||
@ -537,7 +535,7 @@ const ObjectForm = forwardRef(
|
||||
}
|
||||
|
||||
const activityUnsubscribe = subscribeToObjectActivity(
|
||||
id,
|
||||
id?.toLowerCase(),
|
||||
type,
|
||||
activityHandler
|
||||
)
|
||||
@ -565,7 +563,7 @@ const ObjectForm = forwardRef(
|
||||
}
|
||||
|
||||
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
|
||||
id,
|
||||
id?.toLowerCase(),
|
||||
type,
|
||||
objectUpdateHandler
|
||||
)
|
||||
|
||||
@ -693,7 +693,7 @@ const ObjectTable = forwardRef(
|
||||
// Subscribe to new items only
|
||||
newItemIds.forEach((itemId) => {
|
||||
const unsubscribe = subscribeToObjectUpdates(
|
||||
itemId,
|
||||
itemId?.toLowerCase(),
|
||||
type,
|
||||
(updateData) => {
|
||||
updateEventHandlerRef.current(itemId, updateData)
|
||||
|
||||
89
src/components/Dashboard/common/PrinterControlButtons.jsx
Normal file
89
src/components/Dashboard/common/PrinterControlButtons.jsx
Normal 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
|
||||
@ -7,7 +7,12 @@ import merge from 'lodash/merge'
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const PrinterMiscPanel = ({ id, showControls = true, disabled = false }) => {
|
||||
const PrinterMiscPanel = ({
|
||||
id,
|
||||
showControls = true,
|
||||
disabled = false,
|
||||
offline = true
|
||||
}) => {
|
||||
const { subscribeToObjectEvent, connected, sendObjectAction } =
|
||||
useContext(ApiServerContext)
|
||||
const controlsDisabled = disabled || !connected
|
||||
@ -35,10 +40,22 @@ const PrinterMiscPanel = ({ id, showControls = true, disabled = false }) => {
|
||||
const [lcdBrightness, setLcdBrightness] = 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(() => {
|
||||
if (id && connected == true) {
|
||||
const miscEventUnsubscribe = subscribeToObjectEvent(
|
||||
id,
|
||||
id?.toLowerCase(),
|
||||
'printer',
|
||||
'misc',
|
||||
(event) => {
|
||||
@ -193,7 +210,8 @@ const PrinterMiscPanel = ({ id, showControls = true, disabled = false }) => {
|
||||
PrinterMiscPanel.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
showControls: PropTypes.bool,
|
||||
disabled: PropTypes.bool
|
||||
disabled: PropTypes.bool,
|
||||
offline: PropTypes.bool
|
||||
}
|
||||
|
||||
export default PrinterMiscPanel
|
||||
|
||||
@ -30,11 +30,23 @@ const CustomCollapse = styled(Collapse)`
|
||||
}
|
||||
`
|
||||
|
||||
const controlLabelStyle = {
|
||||
whiteSpace: 'nowrap',
|
||||
flexShrink: 0,
|
||||
minWidth: '120px'
|
||||
}
|
||||
|
||||
const controlInputStyle = {
|
||||
flex: 1,
|
||||
minWidth: 0
|
||||
}
|
||||
|
||||
const PrinterPositionPanel = ({
|
||||
id,
|
||||
showControls = true,
|
||||
showMoreInfo = true,
|
||||
disabled = false
|
||||
disabled = false,
|
||||
offline = true
|
||||
}) => {
|
||||
const { subscribeToObjectEvent, connected, sendObjectAction } =
|
||||
useContext(ApiServerContext)
|
||||
@ -55,10 +67,30 @@ const PrinterPositionPanel = ({
|
||||
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(() => {
|
||||
if (id && connected == true) {
|
||||
const motionEventUnsubscribe = subscribeToObjectEvent(
|
||||
id,
|
||||
id?.toLowerCase(),
|
||||
'printer',
|
||||
'motion',
|
||||
(event) => {
|
||||
@ -73,6 +105,7 @@ const PrinterPositionPanel = ({
|
||||
}
|
||||
}
|
||||
}, [id, connected, subscribeToObjectEvent])
|
||||
|
||||
const [speedFactor, setSpeedFactor] = useState(positionData.speedFactor)
|
||||
const [extrudeFactor, setExtrudeFactor] = useState(positionData.extrudeFactor)
|
||||
const [maxVelocity, setMaxVelocity] = useState(positionData.maxVelocity)
|
||||
@ -212,16 +245,22 @@ const PrinterPositionPanel = ({
|
||||
</Descriptions>
|
||||
{showControls && (
|
||||
<>
|
||||
<Space direction='vertical' style={{ width: '100%' }}>
|
||||
<Space direction='horizontal'>
|
||||
<Text>Max Velocity:</Text>
|
||||
<Space.Compact block size='small'>
|
||||
<Flex vertical gap='small' style={{ width: '100%' }}>
|
||||
<Flex align='center' style={{ width: '100%' }}>
|
||||
<Text style={{ ...controlLabelStyle, minWidth: '91px' }}>
|
||||
Max Velocity:
|
||||
</Text>
|
||||
<Space.Compact
|
||||
block
|
||||
size='small'
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
value={round(maxVelocity, 2)}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={5}
|
||||
style={{ width: '125px' }}
|
||||
style={controlInputStyle}
|
||||
suffix='mm/s'
|
||||
onChange={(value) => setMaxVelocity(value)}
|
||||
onPressEnter={handleSetMaxVelocity}
|
||||
@ -237,92 +276,104 @@ const PrinterPositionPanel = ({
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<Space direction='vertical' style={{ width: '100%' }}>
|
||||
<Space direction='horizontal'>
|
||||
<Text>Max Acceleration:</Text>
|
||||
<Space.Compact block size='small'>
|
||||
<InputNumber
|
||||
value={round(maxAcceleration, 2)}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={5}
|
||||
style={{ width: '125px' }}
|
||||
suffix='mm/s²'
|
||||
onChange={(value) => setMaxAcceleration(value)}
|
||||
onPressEnter={handleSetMaxAcceleration}
|
||||
size='small'
|
||||
disabled={controlsDisabled}
|
||||
/>
|
||||
<Button
|
||||
type='default'
|
||||
style={{ width: 40 }}
|
||||
onClick={handleSetMaxAcceleration}
|
||||
disabled={controlsDisabled}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</Space>
|
||||
<Flex align='center' style={{ width: '100%' }}>
|
||||
<Text style={{ ...controlLabelStyle, minWidth: '120px' }}>
|
||||
Max Acceleration:
|
||||
</Text>
|
||||
<Space.Compact
|
||||
block
|
||||
size='small'
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
value={round(maxAcceleration, 2)}
|
||||
min={1}
|
||||
max={10000}
|
||||
step={5}
|
||||
style={controlInputStyle}
|
||||
suffix='mm/s²'
|
||||
onChange={(value) => setMaxAcceleration(value)}
|
||||
onPressEnter={handleSetMaxAcceleration}
|
||||
size='small'
|
||||
disabled={controlsDisabled}
|
||||
/>
|
||||
<Button
|
||||
type='default'
|
||||
style={{ width: 40 }}
|
||||
onClick={handleSetMaxAcceleration}
|
||||
disabled={controlsDisabled}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Flex>
|
||||
|
||||
<Space direction='vertical' style={{ width: '100%' }}>
|
||||
<Space direction='horizontal'>
|
||||
<Text>Sqr Corner Vel:</Text>
|
||||
<Space.Compact block size='small'>
|
||||
<InputNumber
|
||||
value={round(squareCornerVelocity, 2) || 0}
|
||||
min={0.1}
|
||||
max={1000}
|
||||
step={0.1}
|
||||
suffix='mm/s'
|
||||
style={{ width: '125px' }}
|
||||
onChange={(value) => setSquareCornerVelocity(value)}
|
||||
onPressEnter={handleSetSquareCornerVelocity}
|
||||
size='small'
|
||||
disabled={controlsDisabled}
|
||||
/>
|
||||
<Button
|
||||
type='default'
|
||||
style={{ width: 40 }}
|
||||
onClick={handleSetSquareCornerVelocity}
|
||||
disabled={controlsDisabled}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</Space>
|
||||
<Flex align='center' style={{ width: '100%' }}>
|
||||
<Text style={{ ...controlLabelStyle, minWidth: '102px' }}>
|
||||
Sqr Corner Vel:
|
||||
</Text>
|
||||
<Space.Compact
|
||||
block
|
||||
size='small'
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
value={round(squareCornerVelocity, 2) || 0}
|
||||
min={0.1}
|
||||
max={1000}
|
||||
step={0.1}
|
||||
style={controlInputStyle}
|
||||
suffix='mm/s'
|
||||
onChange={(value) => setSquareCornerVelocity(value)}
|
||||
onPressEnter={handleSetSquareCornerVelocity}
|
||||
size='small'
|
||||
disabled={controlsDisabled}
|
||||
/>
|
||||
<Button
|
||||
type='default'
|
||||
style={{ width: 40 }}
|
||||
onClick={handleSetSquareCornerVelocity}
|
||||
disabled={controlsDisabled}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Flex>
|
||||
|
||||
<Space direction='vertical' style={{ width: '100%' }}>
|
||||
<Space direction='horizontal'>
|
||||
<Text>Min Cruise Ratio:</Text>
|
||||
<Space.Compact block size='small'>
|
||||
<InputNumber
|
||||
value={round(minCruiseRatio * 100, 2) || 0}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
suffix='%'
|
||||
style={{ width: '125px' }}
|
||||
onChange={(value) => setMinCruiseRatio(value / 100)}
|
||||
onPressEnter={handleSetMinCruiseRatio}
|
||||
size='small'
|
||||
disabled={controlsDisabled}
|
||||
/>
|
||||
<Button
|
||||
type='default'
|
||||
style={{ width: 40 }}
|
||||
onClick={handleSetMinCruiseRatio}
|
||||
disabled={controlsDisabled}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</Space>
|
||||
</Space>
|
||||
<Flex align='center' style={{ width: '100%' }}>
|
||||
<Text style={{ ...controlLabelStyle, minWidth: '114px' }}>
|
||||
Min Cruise Ratio:
|
||||
</Text>
|
||||
<Space.Compact
|
||||
block
|
||||
size='small'
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
value={round(minCruiseRatio * 100, 2) || 0}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
suffix='%'
|
||||
style={controlInputStyle}
|
||||
onChange={(value) => setMinCruiseRatio(value / 100)}
|
||||
onPressEnter={handleSetMinCruiseRatio}
|
||||
size='small'
|
||||
disabled={controlsDisabled}
|
||||
/>
|
||||
<Button
|
||||
type='default'
|
||||
style={{ width: 40 }}
|
||||
onClick={handleSetMinCruiseRatio}
|
||||
disabled={controlsDisabled}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<Text>Homing Origin:</Text>
|
||||
@ -398,16 +449,22 @@ const PrinterPositionPanel = ({
|
||||
</Descriptions>
|
||||
{showControls && (
|
||||
<>
|
||||
<Space direction='vertical' style={{ width: '100%' }}>
|
||||
<Space direction='horizontal'>
|
||||
<Text>Speed Factor:</Text>
|
||||
<Space.Compact block size='small'>
|
||||
<Flex vertical gap='small' style={{ width: '100%' }}>
|
||||
<Flex align='center' style={{ width: '100%' }}>
|
||||
<Text style={{ ...controlLabelStyle, minWidth: '96px' }}>
|
||||
Speed Factor:
|
||||
</Text>
|
||||
<Space.Compact
|
||||
block
|
||||
size='small'
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
value={round(speedFactor, 2)}
|
||||
min={0.1}
|
||||
max={2}
|
||||
step={0.1}
|
||||
style={{ width: '125px' }}
|
||||
style={controlInputStyle}
|
||||
suffix='%'
|
||||
onChange={(value) => setSpeedFactor(value)}
|
||||
onPressEnter={handleSetSpeedFactor}
|
||||
@ -423,17 +480,23 @@ const PrinterPositionPanel = ({
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</Flex>
|
||||
|
||||
<Space direction='horizontal'>
|
||||
<Text>Extrude Factor:</Text>
|
||||
<Space.Compact block size='small'>
|
||||
<Flex align='center' style={{ width: '100%' }}>
|
||||
<Text style={{ ...controlLabelStyle, minWidth: '105px' }}>
|
||||
Extrude Factor:
|
||||
</Text>
|
||||
<Space.Compact
|
||||
block
|
||||
size='small'
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
>
|
||||
<InputNumber
|
||||
value={round(extrudeFactor, 2)}
|
||||
min={0.1}
|
||||
max={2}
|
||||
step={0.1}
|
||||
style={{ width: '125px' }}
|
||||
style={controlInputStyle}
|
||||
suffix='%'
|
||||
onChange={(value) => setExtrudeFactor(value)}
|
||||
onPressEnter={handleSetExtrudeFactor}
|
||||
@ -449,8 +512,8 @@ const PrinterPositionPanel = ({
|
||||
Set
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</Space>
|
||||
</Space>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
<Descriptions
|
||||
@ -509,7 +572,8 @@ PrinterPositionPanel.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
showControls: PropTypes.bool,
|
||||
showMoreInfo: PropTypes.bool,
|
||||
disabled: PropTypes.bool
|
||||
disabled: PropTypes.bool,
|
||||
offline: PropTypes.bool
|
||||
}
|
||||
|
||||
export default PrinterPositionPanel
|
||||
|
||||
@ -38,7 +38,8 @@ const PrinterTemperaturePanel = ({
|
||||
showExtruder = true,
|
||||
showBed = true,
|
||||
showMoreInfo = true,
|
||||
disabled = false
|
||||
disabled = false,
|
||||
offline = true
|
||||
}) => {
|
||||
const [temperatureData, setTemperatureData] = useState({
|
||||
extruder: {
|
||||
@ -55,6 +56,17 @@ const PrinterTemperaturePanel = ({
|
||||
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 } =
|
||||
useContext(ApiServerContext)
|
||||
const controlsDisabled = disabled || !connected
|
||||
@ -72,9 +84,9 @@ const PrinterTemperaturePanel = ({
|
||||
}, [temperatureData.bed?.target])
|
||||
|
||||
useEffect(() => {
|
||||
if (id && connected == true) {
|
||||
if (id && connected == true && offline == false) {
|
||||
const temperatureEventUnsubscribe = subscribeToObjectEvent(
|
||||
id,
|
||||
id?.toLowerCase(),
|
||||
'printer',
|
||||
'temperature',
|
||||
(event) => {
|
||||
@ -88,7 +100,7 @@ const PrinterTemperaturePanel = ({
|
||||
if (temperatureEventUnsubscribe) temperatureEventUnsubscribe()
|
||||
}
|
||||
}
|
||||
}, [id, connected, subscribeToObjectEvent])
|
||||
}, [id, connected, subscribeToObjectEvent, offline])
|
||||
|
||||
const [extruderTarget, setExtruderTarget] = useState(0)
|
||||
const [bedTarget, setBedTarget] = useState(0)
|
||||
@ -177,7 +189,7 @@ const PrinterTemperaturePanel = ({
|
||||
value={extruderTarget}
|
||||
min={0}
|
||||
max={300}
|
||||
style={{ width: '120px' }}
|
||||
style={{ width: '140px' }}
|
||||
addonAfter='°C'
|
||||
onChange={(value) => setExtruderTarget(value || 0)}
|
||||
onPressEnter={() =>
|
||||
@ -241,7 +253,7 @@ const PrinterTemperaturePanel = ({
|
||||
value={bedTarget}
|
||||
min={0}
|
||||
max={300}
|
||||
style={{ width: '120px' }}
|
||||
style={{ width: '140px' }}
|
||||
addonAfter='°C'
|
||||
onChange={(value) => setBedTarget(value || 0)}
|
||||
onPressEnter={() =>
|
||||
@ -309,7 +321,8 @@ PrinterTemperaturePanel.propTypes = {
|
||||
showBed: PropTypes.bool,
|
||||
showMoreInfo: PropTypes.bool,
|
||||
shouldUnsubscribe: PropTypes.bool,
|
||||
disabled: PropTypes.bool
|
||||
disabled: PropTypes.bool,
|
||||
offline: PropTypes.bool
|
||||
}
|
||||
|
||||
export default PrinterTemperaturePanel
|
||||
|
||||
@ -42,7 +42,6 @@ const Thumbnail = function Thumbnail({
|
||||
borderRadius: '5px',
|
||||
width: size,
|
||||
height: size,
|
||||
border: 'none',
|
||||
...style
|
||||
}
|
||||
|
||||
@ -151,7 +150,7 @@ const Thumbnail = function Thumbnail({
|
||||
<Card
|
||||
className={className}
|
||||
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%' }}>
|
||||
{fallback || (
|
||||
|
||||
@ -1510,7 +1510,7 @@ const ApiServerProvider = ({ children }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchFileThumbnail = async (file, size) => {
|
||||
const fetchFileThumbnail = async (file, size, show404Error = false) => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${config.backendUrl}/files/${file._id}/thumbnail`,
|
||||
@ -1528,6 +1528,9 @@ const ApiServerProvider = ({ children }) => {
|
||||
})
|
||||
return window.URL.createObjectURL(blob)
|
||||
} catch (err) {
|
||||
if (err.response.status === 404 && show404Error == false) {
|
||||
return null
|
||||
}
|
||||
console.error(err)
|
||||
showError(err, () => {
|
||||
fetchFileThumbnail(file, size)
|
||||
|
||||
@ -164,6 +164,7 @@ export const GCodeFile = {
|
||||
showPreview: false,
|
||||
showHyperlink: true,
|
||||
masterFilter: ['.gcode', '.g'],
|
||||
thumbnail: true,
|
||||
columnWidth: 200
|
||||
},
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user