Refactored application to replace SocketContext with PrintServerContext for WebSocket communication. Updated related components to utilize print server events for real-time updates. Removed unused SocketContext and NotificationContext files. Updated package dependencies to use loglevel instead of log4js for improved logging capabilities.

This commit is contained in:
Tom Butcher 2025-06-29 02:28:01 +01:00
parent 8caad73f0b
commit 0634919bb6
43 changed files with 1318 additions and 1065 deletions

80
package-lock.json generated
View File

@ -26,7 +26,7 @@
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"gcode-preview": "^2.18.0", "gcode-preview": "^2.18.0",
"keycloak-js": "^26.2.0", "keycloak-js": "^26.2.0",
"log4js": "^6.9.1", "loglevel": "^1.9.2",
"moment": "*", "moment": "*",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-eslint": "^16.4.2", "prettier-eslint": "^16.4.2",
@ -9986,15 +9986,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/date-format": {
"version": "4.0.14",
"resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz",
"integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.11.13", "version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
@ -16008,27 +15999,10 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/log4js": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz",
"integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==",
"license": "Apache-2.0",
"dependencies": {
"date-format": "^4.0.14",
"debug": "^4.3.4",
"flatted": "^3.2.7",
"rfdc": "^1.3.0",
"streamroller": "^3.1.5"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/loglevel": { "node_modules/loglevel": {
"version": "1.9.2", "version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6.0" "node": ">= 0.6.0"
@ -21486,12 +21460,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"license": "MIT"
},
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -22819,52 +22787,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/streamroller": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz",
"integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==",
"license": "MIT",
"dependencies": {
"date-format": "^4.0.14",
"debug": "^4.3.4",
"fs-extra": "^8.1.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/streamroller/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/streamroller/node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"license": "MIT",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/streamroller/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",

View File

@ -21,7 +21,7 @@
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"gcode-preview": "^2.18.0", "gcode-preview": "^2.18.0",
"keycloak-js": "^26.2.0", "keycloak-js": "^26.2.0",
"log4js": "^6.9.1", "loglevel": "^1.9.2",
"moment": "*", "moment": "*",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-eslint": "^16.4.2", "prettier-eslint": "^16.4.2",

View File

@ -22,11 +22,14 @@
.ant-input, .ant-input,
.ant-input-number .ant-input-number-input, .ant-input-number .ant-input-number-input,
.ant-segmented-item-label, .ant-segmented-item-label,
.ant-badge-status-text { .ant-badge-status-text,
.ant-tree-title,
.ant-select {
font-family: 'DM Sans'; font-family: 'DM Sans';
} }
.ant-typography code { .ant-typography code,
.ant-typography pre {
font-family: 'DM Mono'; font-family: 'DM Mono';
} }
@ -115,6 +118,10 @@ code {
padding: 0 !important; padding: 0 !important;
} }
.ant-popover-inner:has(.keyboard-shortcut-tooltip) {
padding: 8px !important;
}
/* --- Start of src/index.css --- */ /* --- Start of src/index.css --- */
body { body {
margin: 0; margin: 0;

View File

@ -44,7 +44,7 @@ import StockAuditInfo from './components/Dashboard/Inventory/StockAudits/StockAu
import Dashboard from './components/Dashboard/Dashboard.jsx' import Dashboard from './components/Dashboard/Dashboard.jsx'
import PrivateRoute from './components/PrivateRoute' import PrivateRoute from './components/PrivateRoute'
import './App.css' import './App.css'
import { SocketProvider } from './components/Dashboard/context/SocketContext.js' import { PrintServerProvider } from './components/Dashboard/context/PrintServerContext.js'
import { AuthProvider } from './components/Dashboard/context/AuthContext.js' import { AuthProvider } from './components/Dashboard/context/AuthContext.js'
import { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.js' import { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.js'
import StockEvents from './components/Dashboard/Inventory/StockEvents.jsx' import StockEvents from './components/Dashboard/Inventory/StockEvents.jsx'
@ -61,8 +61,8 @@ import NoteTypes from './components/Dashboard/Management/NoteTypes.jsx'
import NoteTypeInfo from './components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx' import NoteTypeInfo from './components/Dashboard/Management/NoteTypes/NoteTypeInfo.jsx'
import SessionStorage from './components/Dashboard/Developer/SessionStorage.jsx' import SessionStorage from './components/Dashboard/Developer/SessionStorage.jsx'
import AuthContextDebug from './components/Dashboard/Developer/AuthContextDebug.jsx' import AuthContextDebug from './components/Dashboard/Developer/AuthContextDebug.jsx'
import SocketContextDebug from './components/Dashboard/Developer/SocketContextDebug.jsx' import PrintServerContextDebug from './components/Dashboard/Developer/PrintServerContextDebug.jsx'
import { NotificationProvider } from './components/Dashboard/context/NotificationContext.js' import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.js'
import Users from './components/Dashboard/Management/Users.jsx' import Users from './components/Dashboard/Management/Users.jsx'
import UserInfo from './components/Dashboard/Management/Users/UserInfo.jsx' import UserInfo from './components/Dashboard/Management/Users/UserInfo.jsx'
@ -74,8 +74,8 @@ const AppContent = () => {
<App> <App>
<AuthProvider> <AuthProvider>
<Router> <Router>
<SocketProvider> <PrintServerProvider>
<NotificationProvider> <ApiServerProvider>
<SpotlightProvider> <SpotlightProvider>
<Routes> <Routes>
<Route <Route
@ -213,8 +213,8 @@ const AppContent = () => {
element={<AuthContextDebug />} element={<AuthContextDebug />}
/> />
<Route <Route
path='developer/socketcontextdebug' path='developer/printservercontextdebug'
element={<SocketContextDebug />} element={<PrintServerContextDebug />}
/> />
</Route> </Route>
<Route <Route
@ -228,8 +228,8 @@ const AppContent = () => {
/> />
</Routes> </Routes>
</SpotlightProvider> </SpotlightProvider>
</NotificationProvider> </ApiServerProvider>
</SocketProvider> </PrintServerProvider>
</Router> </Router>
</AuthProvider> </AuthProvider>
</App> </App>

Binary file not shown.

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2" viewBox="0 0 64 64"><path d="M7.234 62.237h29.263c4.746 0 7.234-2.544 7.234-7.668V32.436c0-5.08-2.488-7.625-7.234-7.625H7.234C2.483 24.811 0 27.356 0 32.436v22.133c0 5.124 2.483 7.668 7.234 7.668m.887-5.601c-1.299 0-2.04-.793-2.04-2.268V32.649c0-1.48.741-2.236 2.04-2.236h27.494c1.324 0 2.029.756 2.029 2.236v21.719c0 1.475-.705 2.268-2.029 2.268zm-2.405-29.2h5.852V16.922c0-7.338 4.694-11.326 10.285-11.326 5.579 0 10.341 3.988 10.341 11.326v10.514h5.826v-9.959C38.02 5.946 30.389 0 21.853 0 13.342 0 5.716 5.946 5.716 17.477z" style="fill-rule:nonzero" transform="translate(11.623 3)scale(.93191)"/></svg>

After

Width:  |  Height:  |  Size: 754 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.931914,0,0,0.931914,11.6234,3)">
<path d="M7.234,62.237L36.497,62.237C41.243,62.237 43.731,59.693 43.731,54.569L43.731,32.436C43.731,27.356 41.243,24.811 36.497,24.811L7.234,24.811C2.483,24.811 0,27.356 0,32.436L0,54.569C0,59.693 2.483,62.237 7.234,62.237ZM8.121,56.636C6.822,56.636 6.081,55.843 6.081,54.368L6.081,32.649C6.081,31.169 6.822,30.413 8.121,30.413L35.615,30.413C36.939,30.413 37.644,31.169 37.644,32.649L37.644,54.368C37.644,55.843 36.939,56.636 35.615,56.636L8.121,56.636ZM5.716,27.436L11.568,27.436L11.568,16.922C11.568,9.584 16.262,5.596 21.853,5.596C27.432,5.596 32.194,9.584 32.194,16.922L32.194,27.436L38.02,27.436L38.02,17.477C38.02,5.946 30.389,0 21.853,0C13.342,0 5.716,5.946 5.716,17.477L5.716,27.436Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,29 +1,35 @@
import React from 'react' import React from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar' import DashboardSidebar from '../common/DashboardSidebar'
import { Typography } from 'antd'
const { Text } = Typography
const items = [ const items = [
{ {
key: 'sessionstorage', key: 'sessionstorage',
label: 'Session Storage', label: 'Session Storage',
icon: <Text>🗃</Text>,
path: '/dashboard/developer/sessionstorage' path: '/dashboard/developer/sessionstorage'
}, },
{ {
key: 'authcontextdebug', key: 'authcontextdebug',
label: 'Auth Context Debug', label: 'Auth Debug',
icon: <Text>🔐</Text>,
path: '/dashboard/developer/authcontextdebug' path: '/dashboard/developer/authcontextdebug'
}, },
{ {
key: 'socketcontextdebug', key: 'printservercontextdebug',
label: 'Socket Context Debug', label: 'Print Server Debug',
path: '/dashboard/developer/socketcontextdebug' icon: <Text>🖨</Text>,
path: '/dashboard/developer/printservercontextdebug'
} }
] ]
const routeKeyMap = { const routeKeyMap = {
'/dashboard/developer/sessionstorage': 'sessionstorage', '/dashboard/developer/sessionstorage': 'sessionstorage',
'/dashboard/developer/authcontext': 'authcontextdebug', '/dashboard/developer/authcontext': 'authcontextdebug',
'/dashboard/developer/socketcontext': 'socketcontextdebug' '/dashboard/developer/printservercontext': 'printservercontextdebug'
} }
const DeveloperSidebar = (props) => { const DeveloperSidebar = (props) => {

View File

@ -9,13 +9,13 @@ import {
message message
} from 'antd' } from 'antd'
import ReloadIcon from '../../Icons/ReloadIcon.jsx' import ReloadIcon from '../../Icons/ReloadIcon.jsx'
import { SocketContext } from '../context/SocketContext.js' import { PrintServerContext } from '../context/PrintServerContext.js'
import BoolDisplay from '../common/BoolDisplay.jsx' import BoolDisplay from '../common/BoolDisplay.jsx'
const { Text, Paragraph } = Typography const { Text, Paragraph } = Typography
const SocketContextDebug = () => { const PrintServerContextDebug = () => {
const { socket, error, connecting } = useContext(SocketContext) const { printServer, error, connecting } = useContext(PrintServerContext)
const [msgApi, contextHolder] = message.useMessage() const [msgApi, contextHolder] = message.useMessage()
const actionItems = { const actionItems = {
@ -36,9 +36,9 @@ const SocketContextDebug = () => {
// Helper to display socket info safely // Helper to display socket info safely
const getSocketInfo = () => { const getSocketInfo = () => {
if (!socket) return 'n/a' if (!printServer) return 'n/a'
// Only show safe properties // Only show safe properties
const { id, connected, disconnected, nsp } = socket const { id, connected, disconnected, nsp } = printServer
return JSON.stringify({ id, connected, disconnected, nsp }, null, 2) return JSON.stringify({ id, connected, disconnected, nsp }, null, 2)
} }
@ -55,7 +55,7 @@ const SocketContextDebug = () => {
<div style={{ height: '100%', overflow: 'auto' }}> <div style={{ height: '100%', overflow: 'auto' }}>
<Descriptions bordered column={1}> <Descriptions bordered column={1}>
<Descriptions.Item label='Connected'> <Descriptions.Item label='Connected'>
<BoolDisplay value={socket?.connected || false} /> <BoolDisplay value={printServer?.connected || false} />
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label='Connecting'> <Descriptions.Item label='Connecting'>
<BoolDisplay value={connecting} /> <BoolDisplay value={connecting} />
@ -74,4 +74,4 @@ const SocketContextDebug = () => {
) )
} }
export default SocketContextDebug export default PrintServerContextDebug

View File

@ -16,7 +16,7 @@ import {
} from 'antd' } from 'antd'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import NewFilamentStock from './FilamentStocks/NewFilamentStock' import NewFilamentStock from './FilamentStocks/NewFilamentStock'
import IdText from '../common/IdText' import IdText from '../common/IdText'
@ -41,7 +41,7 @@ const { Text } = Typography
const FilamentStocks = () => { const FilamentStocks = () => {
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate() const navigate = useNavigate()
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const tableRef = useRef() const tableRef = useRef()
@ -213,9 +213,9 @@ const FilamentStocks = () => {
) )
React.useEffect(() => { React.useEffect(() => {
if (socket && !initialized) { if (printServer && !initialized) {
setInitialized(true) setInitialized(true)
socket.on('notify_filamentstock_update', (updateData) => { printServer.on('notify_filamentstock_update', (updateData) => {
console.log('Received filament stock update:', updateData) console.log('Received filament stock update:', updateData)
if (tableRef.current) { if (tableRef.current) {
tableRef.current.updateData(updateData._id, updateData) tableRef.current.updateData(updateData._id, updateData)
@ -224,12 +224,12 @@ const FilamentStocks = () => {
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('Deregistering filament stock update listener') console.log('Deregistering filament stock update listener')
socket.off('notify_filamentstock_update') printServer.off('notify_filamentstock_update')
} }
} }
}, [socket, initialized]) }, [printServer, initialized])
const getFilamentStockActionItems = (id) => { const getFilamentStockActionItems = (id) => {
return { return {

View File

@ -19,7 +19,7 @@ import {
} from 'antd' } from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import IdText from '../../common/IdText' import IdText from '../../common/IdText'
import { SocketContext } from '../../context/SocketContext' import { PrintServerContext } from '../../context/PrintServerContext'
import FilamentStockState from '../../common/FilamentStockState' import FilamentStockState from '../../common/FilamentStockState'
import StockEventTable from '../../common/StockEventTable' import StockEventTable from '../../common/StockEventTable'
import useCollapseState from '../../hooks/useCollapseState' import useCollapseState from '../../hooks/useCollapseState'
@ -48,7 +48,7 @@ const FilamentStockInfo = () => {
'filamentStockId' 'filamentStockId'
) )
const [form] = Form.useForm() const [form] = Form.useForm()
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [collapseState, updateCollapseState] = useCollapseState( const [collapseState, updateCollapseState] = useCollapseState(
'FilamentStockInfo', 'FilamentStockInfo',
{ {
@ -75,9 +75,9 @@ const FilamentStockInfo = () => {
// Add WebSocket event listener for real-time updates // Add WebSocket event listener for real-time updates
useEffect(() => { useEffect(() => {
if (socket && !initialized && filamentStockId) { if (printServer && !initialized && filamentStockId) {
setInitialized(true) setInitialized(true)
socket.on('notify_filamentstock_update', (statusUpdate) => { printServer.on('notify_filamentstock_update', (statusUpdate) => {
console.log('GOT FILAMENT STOCK UPDATE', statusUpdate) console.log('GOT FILAMENT STOCK UPDATE', statusUpdate)
setFilamentStockData((prevData) => { setFilamentStockData((prevData) => {
if (statusUpdate?._id === filamentStockId) { if (statusUpdate?._id === filamentStockId) {
@ -91,12 +91,12 @@ const FilamentStockInfo = () => {
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('Deregistering filament stock update listener') console.log('Deregistering filament stock update listener')
socket.off('notify_filamentstock_update') printServer.off('notify_filamentstock_update')
} }
} }
}, [socket, initialized, filamentStockId]) }, [printServer, initialized, filamentStockId])
const fetchFilamentStockDetails = async () => { const fetchFilamentStockDetails = async () => {
try { try {

View File

@ -11,7 +11,7 @@ import {
} from 'antd' } from 'antd'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { SocketContext } from '../../context/SocketContext' import { PrintServerContext } from '../../context/PrintServerContext'
import FilamentStockSelect from '../../common/FilamentStockSelect' import FilamentStockSelect from '../../common/FilamentStockSelect'
import PrinterSelect from '../../common/PrinterSelect' import PrinterSelect from '../../common/PrinterSelect'
@ -38,7 +38,7 @@ const LoadFilamentStock = ({
filamentStockLoaded: PropTypes.bool filamentStockLoaded: PropTypes.bool
} }
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const initialLoadFilamentStockForm = { const initialLoadFilamentStockForm = {
printer: printer, printer: printer,
@ -93,16 +93,16 @@ const LoadFilamentStock = ({
console.log(statusUpdate) console.log(statusUpdate)
} }
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
socket.on('notify_status_update', notifyStatusUpdate) printServer.on('notify_status_update', notifyStatusUpdate)
return () => { return () => {
socket.off('notify_status_update', notifyStatusUpdate) printServer.off('notify_status_update', notifyStatusUpdate)
socket.emit('printer.objects.unsubscribe', params) printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [socket, loadFilamentStockFormValues.printer]) }, [printServer, loadFilamentStockFormValues.printer])
React.useEffect(() => { React.useEffect(() => {
loadFilamentStockForm loadFilamentStockForm
@ -170,7 +170,7 @@ const LoadFilamentStock = ({
try { try {
// Set the extruder temperature // Set the extruder temperature
await socket.emit('printer.filamentstock.load', { await printServer.emit('printer.filamentstock.load', {
printerId: loadFilamentStockFormValues.printer._id, printerId: loadFilamentStockFormValues.printer._id,
filamentStockId: loadFilamentStockFormValues.filamentStock._id filamentStockId: loadFilamentStockFormValues.filamentStock._id
}) })

View File

@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect } from 'react'
import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd' import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { SocketContext } from '../../context/SocketContext' import { PrintServerContext } from '../../context/PrintServerContext'
import PrinterSelect from '../../common/PrinterSelect' import PrinterSelect from '../../common/PrinterSelect'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
@ -18,7 +18,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
printer: PropTypes.object printer: PropTypes.object
} }
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const isMobile = useMediaQuery({ maxWidth: 768 }) const isMobile = useMediaQuery({ maxWidth: 768 })
const initialUnloadFilamentStockForm = { const initialUnloadFilamentStockForm = {
@ -66,16 +66,16 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
} }
} }
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
socket.on('notify_status_update', notifyStatusUpdate) printServer.on('notify_status_update', notifyStatusUpdate)
return () => { return () => {
socket.off('notify_status_update', notifyStatusUpdate) printServer.off('notify_status_update', notifyStatusUpdate)
socket.emit('printer.objects.unsubscribe', params) printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [socket, unloadFilamentStockFormValues.printer]) }, [printServer, unloadFilamentStockFormValues.printer])
React.useEffect(() => { React.useEffect(() => {
if (reset) { if (reset) {
@ -109,7 +109,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
const handleUnloadFilamentStock = async () => { const handleUnloadFilamentStock = async () => {
setUnloadFilamentStockLoading(true) setUnloadFilamentStockLoading(true)
// Send G-code to retract the filament // Send G-code to retract the filament
await socket.emit('printer.gcode.script', { await printServer.emit('printer.gcode.script', {
printerId: unloadFilamentStockFormValues.printer._id, printerId: unloadFilamentStockFormValues.printer._id,
script: `_CLIENT_LINEAR_MOVE E=-200 F=1000` script: `_CLIENT_LINEAR_MOVE E=-200 F=1000`
}) })

View File

@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { Button, Flex, Space, message, Dropdown, Typography } from 'antd' import { Button, Flex, Space, message, Dropdown, Typography } from 'antd'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import IdText from '../common/IdText' import IdText from '../common/IdText'
import StockAuditIcon from '../../Icons/StockAuditIcon' import StockAuditIcon from '../../Icons/StockAuditIcon'
@ -20,16 +20,16 @@ const { Text } = Typography
const StockAudits = () => { const StockAudits = () => {
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate() const navigate = useNavigate()
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const tableRef = useRef() const tableRef = useRef()
const { authenticated } = useContext(AuthContext) const { authenticated } = useContext(AuthContext)
React.useEffect(() => { React.useEffect(() => {
if (socket && !initialized) { if (printServer && !initialized) {
setInitialized(true) setInitialized(true)
socket.on('notify_stockaudit_update', (updateData) => { printServer.on('notify_stockaudit_update', (updateData) => {
console.log('Received stock audit update:', updateData) console.log('Received stock audit update:', updateData)
if (tableRef.current) { if (tableRef.current) {
tableRef.current.updateData(updateData._id, updateData) tableRef.current.updateData(updateData._id, updateData)
@ -38,12 +38,12 @@ const StockAudits = () => {
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('Deregistering stock audit update listener') console.log('Deregistering stock audit update listener')
socket.off('notify_stockaudit_update') printServer.off('notify_stockaudit_update')
} }
} }
}, [socket, initialized]) }, [printServer, initialized])
const getStockAuditActionItems = (id) => { const getStockAuditActionItems = (id) => {
return { return {

View File

@ -11,7 +11,7 @@ import {
} from 'antd' } from 'antd'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import IdText from '../common/IdText' import IdText from '../common/IdText'
import TimeDisplay from '../common/TimeDisplay' import TimeDisplay from '../common/TimeDisplay'
import ReloadIcon from '../../Icons/ReloadIcon' import ReloadIcon from '../../Icons/ReloadIcon'
@ -31,7 +31,7 @@ import StockEventIcon from '../../Icons/StockEventIcon'
const { Text } = Typography const { Text } = Typography
const StockEvents = () => { const StockEvents = () => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const tableRef = useRef() const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('StockEvents') const [viewMode, setViewMode] = useViewMode('StockEvents')
@ -225,9 +225,9 @@ const StockEvents = () => {
React.useEffect(() => { React.useEffect(() => {
// Add WebSocket event listener for real-time updates // Add WebSocket event listener for real-time updates
if (socket && !initialized) { if (printServer && !initialized) {
setInitialized(true) setInitialized(true)
socket.on('notify_stockevent_update', (updateData) => { printServer.on('notify_stockevent_update', (updateData) => {
console.log('Received stock event update:', updateData) console.log('Received stock event update:', updateData)
if (tableRef.current) { if (tableRef.current) {
tableRef.current.updateData(updateData._id, updateData) tableRef.current.updateData(updateData._id, updateData)
@ -236,12 +236,12 @@ const StockEvents = () => {
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('Deregistering stock event update listener') console.log('Deregistering stock event update listener')
socket.off('notify_stockevent_update') printServer.off('notify_stockevent_update')
} }
} }
}, [socket, initialized]) }, [printServer, initialized])
const actionItems = { const actionItems = {
items: [ items: [

View File

@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect, useContext, useCallback } from 'react'
import { useLocation } from 'react-router-dom' import { useLocation } from 'react-router-dom'
import axios from 'axios'
import { import {
Descriptions, Descriptions,
Spin, Spin,
@ -15,13 +14,15 @@ import {
InputNumber, InputNumber,
ColorPicker, ColorPicker,
Select, Select,
Collapse,
Dropdown, Dropdown,
Popover, Popover,
Checkbox, Checkbox,
Card Card,
Tag
} from 'antd' } from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config'
import IdText from '../../common/IdText' import IdText from '../../common/IdText'
import ReloadIcon from '../../../Icons/ReloadIcon' import ReloadIcon from '../../../Icons/ReloadIcon'
import EditIcon from '../../../Icons/EditIcon.jsx' import EditIcon from '../../../Icons/EditIcon.jsx'
@ -32,19 +33,25 @@ import VendorSelect from '../../common/VendorSelect'
import useCollapseState from '../../hooks/useCollapseState' import useCollapseState from '../../hooks/useCollapseState'
import AuditLogTable from '../../common/AuditLogTable' import AuditLogTable from '../../common/AuditLogTable'
import DashboardNotes from '../../common/DashboardNotes' import DashboardNotes from '../../common/DashboardNotes'
import InfoCollapse from '../../common/InfoCollapse'
import config from '../../../../config.js'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx' import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import LockIcon from '../../../Icons/LockIcon.jsx'
import { ApiServerContext } from '../../context/ApiServerContext'
const { Title, Link, Text } = Typography const log = loglevel.getLogger('FilamentInfo')
log.setLevel(config.logLevel)
const { Link, Text } = Typography
const FilamentInfo = () => { const FilamentInfo = () => {
const [filamentData, setFilamentData] = useState(null) const [filamentData, setFilamentData] = useState(null)
const [fetchLoading, setFetchLoading] = useState(true) const [fetchLoading, setFetchLoading] = useState(true)
const [editLoading, setEditLoading] = useState(false) const [editLoading, setEditLoading] = useState(false)
const [error, setError] = useState(null) const [lockUser, setLockUser] = useState(null)
const [initialized, setInitialized] = useState(false)
const location = useLocation() const location = useLocation()
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const filamentId = new URLSearchParams(location.search).get('filamentId') const filamentId = new URLSearchParams(location.search).get('filamentId')
@ -59,12 +66,36 @@ const FilamentInfo = () => {
auditLogs: true auditLogs: true
} }
) )
const {
apiServer,
fetchObjectInfo,
updateObjectInfo,
lockObject,
unlockObject,
onLockEvent,
onUpdateEvent,
fetchObjectLock,
showError
} = useContext(ApiServerContext)
useEffect(() => { // Define the event handler function
if (filamentId) { const lockEventHandler = useCallback((lockEvent) => {
fetchFilamentDetails() if (lockEvent.locked === true) {
setLockUser(lockEvent.user)
} else {
setLockUser(null)
} }
}, [filamentId]) }, [])
// Cleanup effect for component unmount
useEffect(() => {
return () => {
if (filamentId) {
// Ensure any remaining locks are released when component unmounts
unlockObject(filamentId, 'filament')
}
}
}, [filamentId, unlockObject])
useEffect(() => { useEffect(() => {
if (filamentData) { if (filamentData) {
@ -83,32 +114,84 @@ const FilamentInfo = () => {
} }
}, [filamentData, form]) }, [filamentData, form])
const fetchFilamentDetails = async () => { const fetchFilamentInfo = useCallback(async () => {
try { try {
setFetchLoading(true) setFetchLoading(true)
const response = await axios.get( const data = await fetchObjectInfo(filamentId, 'filament')
`${config.backendUrl}/filaments/${filamentId}`, const lockEvent = await fetchObjectLock(filamentId, 'filament')
{ setLockUser(lockEvent?.user || null)
headers: { setFilamentData(data)
Accept: 'application/json' form.setFieldsValue(data)
},
withCredentials: true
}
)
setFilamentData(response.data)
form.setFieldsValue(response.data)
setError(null)
} catch (err) {
setError('Failed to fetch filament details')
messageApi.error('Failed to fetch filament details')
} finally {
setFetchLoading(false) setFetchLoading(false)
} catch (err) {
messageApi.error('Failed to fetch filament info')
// Show error modal with retry functionality
showError(
`Failed to fetch filament information. Message: ${err.message}. Code: ${err.code}`,
fetchFilamentInfo
)
} }
}, [
fetchObjectInfo,
fetchObjectLock,
filamentId,
form,
messageApi,
showError
])
const updateFilamentInfo = async () => {
const values = form.getFieldsValue()
const updateValue = {
name: values.name,
vendor: values.vendor,
type: values.type,
cost: values.cost,
color: values.color,
diameter: values.diameter,
density: values.density,
url: values.url,
barcode: values.barcode,
emptySpoolWeight: values.emptySpoolWeight
} }
await updateObjectInfo(filamentId, 'filament', updateValue)
}
// Define the update event handler function
const updateEventHandler = useCallback(
(updateEvent) => {
log.debug('Update event received for filament:', updateEvent)
// Refresh the filament data when an update is received
fetchFilamentInfo()
},
[fetchFilamentInfo]
)
useEffect(() => {
if (initialized == false && filamentId && apiServer?.connected === true) {
setInitialized(true)
fetchFilamentInfo()
}
}, [filamentId, apiServer?.connected, initialized, fetchFilamentInfo])
useEffect(() => {
if (filamentId) {
const cleanup = onLockEvent(filamentId, lockEventHandler)
return cleanup
}
}, [filamentId, onLockEvent, lockEventHandler])
useEffect(() => {
if (filamentId) {
const cleanup = onUpdateEvent(filamentId, updateEventHandler)
return cleanup
}
}, [filamentId, onUpdateEvent, updateEventHandler])
const startEditing = () => { const startEditing = () => {
updateCollapseState('info', true) updateCollapseState('info', true)
setIsEditing(true) setIsEditing(true)
lockObject(filamentId, 'filament')
} }
const cancelEditing = () => { const cancelEditing = () => {
@ -128,34 +211,15 @@ const FilamentInfo = () => {
}) })
} }
setIsEditing(false) setIsEditing(false)
unlockObject(filamentId, 'filament')
} }
const updateFilamentInfo = async () => { const handleUpdateFilamentInfo = async () => {
try { try {
const values = await form.validateFields() const values = await form.validateFields()
setEditLoading(true) setEditLoading(true)
await axios.put( await updateFilamentInfo()
`${config.backendUrl}/filaments/${filamentId}`,
{
name: values.name,
vendor: values.vendor,
type: values.type,
cost: values.cost,
color: values.color,
diameter: values.diameter,
density: values.density,
url: values.url,
barcode: values.barcode,
emptySpoolWeight: values.emptySpoolWeight
},
{
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
}
)
// Update the local state with the new values // Update the local state with the new values
setFilamentData({ ...filamentData, ...values }) setFilamentData({ ...filamentData, ...values })
@ -168,8 +232,13 @@ const FilamentInfo = () => {
} }
console.error('Failed to update filament information:', err) console.error('Failed to update filament information:', err)
messageApi.error('Failed to update filament information') messageApi.error('Failed to update filament information')
// Show error modal with retry functionality
showError(
`Failed to update filament information. Message: ${err.message}. Code: ${err.code}`,
() => handleUpdateFilamentInfo()
)
} finally { } finally {
fetchFilamentDetails() fetchFilamentInfo()
setEditLoading(false) setEditLoading(false)
} }
} }
@ -184,7 +253,7 @@ const FilamentInfo = () => {
], ],
onClick: ({ key }) => { onClick: ({ key }) => {
if (key === 'reload') { if (key === 'reload') {
fetchFilamentDetails() fetchFilamentInfo()
} }
} }
} }
@ -215,20 +284,6 @@ const FilamentInfo = () => {
) )
} }
if (error) {
return (
<Space
direction='vertical'
style={{ width: '100%', textAlign: 'center' }}
>
<p>{error || 'Filament not found'}</p>
<Button icon={<ReloadIcon />} onClick={fetchFilamentDetails}>
Retry
</Button>
</Space>
)
}
return ( return (
<> <>
{contextHolder} {contextHolder}
@ -238,25 +293,42 @@ const FilamentInfo = () => {
style={{ height: '100%', minHeight: 0 }} style={{ height: '100%', minHeight: 0 }}
> >
<Flex justify={'space-between'}> <Flex justify={'space-between'}>
<Space size='middle'>
<Space size='small'> <Space size='small'>
<Dropdown menu={actionItems}> <Dropdown menu={actionItems}>
<Button>Actions</Button> <Button disabled={fetchLoading}>Actions</Button>
</Dropdown> </Dropdown>
<Popover <Popover
content={getViewDropdownItems()} content={getViewDropdownItems()}
placement='bottomLeft' placement='bottomLeft'
arrow={false} arrow={false}
> >
<Button>View</Button> <Button disabled={fetchLoading}>View</Button>
</Popover> </Popover>
</Space> </Space>
{lockUser && (
<Flex gap={'small'} align='center'>
<Tag
icon={<LockIcon />}
style={{ margin: 0 }}
color={'orange'}
/>
<IdText
id={lockUser}
type={'user'}
longId={false}
showCopy={false}
/>
</Flex>
)}
</Space>
<Space> <Space>
{isEditing ? ( {isEditing ? (
<> <>
<Button <Button
icon={<CheckIcon />} icon={<CheckIcon />}
type='primary' type='primary'
onClick={updateFilamentInfo} onClick={handleUpdateFilamentInfo}
loading={editLoading} loading={editLoading}
disabled={editLoading} disabled={editLoading}
/> />
@ -267,46 +339,24 @@ const FilamentInfo = () => {
/> />
</> </>
) : ( ) : (
<Button icon={<EditIcon />} onClick={startEditing} /> <Button
icon={<EditIcon />}
onClick={startEditing}
disabled={lockUser !== null || fetchLoading}
/>
)} )}
</Space> </Space>
</Flex> </Flex>
{error ? (
<Space
direction='vertical'
style={{ width: '100%', textAlign: 'center' }}
>
<p>{error || 'Print job not found'}</p>
<Button icon={<ReloadIcon />} onClick={fetchFilamentDetails}>
Retry
</Button>
</Space>
) : (
<div style={{ height: '100%', overflow: 'auto' }}> <div style={{ height: '100%', overflow: 'auto' }}>
<Flex vertical gap={'large'}> <Flex vertical gap={'large'}>
<Collapse <InfoCollapse
ghost title='Filament Information'
expandIconPosition='end' icon={<InfoCircleIcon />}
activeKey={collapseState.info ? ['1'] : []} active={collapseState.info}
onChange={(keys) => onToggle={(expanded) => updateCollapseState('info', expanded)}
updateCollapseState('info', keys.length > 0) className='no-t-padding-collapse'
} key='info'
expandIcon={({ isActive }) => (
<CaretLeftOutlined rotate={isActive ? -90 : 0} />
)}
className='no-h-padding-collapse no-t-padding-collapse'
>
<Collapse.Panel
header={
<Flex align='center' gap={'middle'}>
<InfoCircleIcon />
<Title level={5} style={{ margin: 0 }}>
Filament Information
</Title>
</Flex>
}
key='1'
> >
<Form <Form
form={form} form={form}
@ -323,10 +373,7 @@ const FilamentInfo = () => {
barcode: filamentData?.barcode || '' barcode: filamentData?.barcode || ''
}} }}
> >
<Spin <Spin indicator={<LoadingOutlined />} spinning={fetchLoading}>
indicator={<LoadingOutlined />}
spinning={fetchLoading}
>
<Descriptions <Descriptions
bordered bordered
column={{ column={{
@ -519,10 +566,7 @@ const FilamentInfo = () => {
} }
]} ]}
> >
<InputNumber <InputNumber suffix='mm' style={{ width: '100%' }} />
suffix='mm'
style={{ width: '100%' }}
/>
</Form.Item> </Form.Item>
) : filamentData?.diameter ? ( ) : filamentData?.diameter ? (
<Text>{`${filamentData.diameter}mm`}</Text> <Text>{`${filamentData.diameter}mm`}</Text>
@ -583,58 +627,26 @@ const FilamentInfo = () => {
</Descriptions> </Descriptions>
</Spin> </Spin>
</Form> </Form>
</Collapse.Panel> </InfoCollapse>
</Collapse>
<Collapse <InfoCollapse
ghost title='Notes'
expandIconPosition='end' icon={<NoteIcon />}
activeKey={collapseState.notes ? ['notes'] : []} active={collapseState.notes}
onChange={(keys) => onToggle={(expanded) => updateCollapseState('notes', expanded)}
updateCollapseState('notes', keys.length > 0)
}
expandIcon={({ isActive }) => (
<CaretLeftOutlined rotate={isActive ? -90 : 0} />
)}
className='no-h-padding-collapse'
>
<Collapse.Panel
header={
<Flex align='center' gap={'middle'}>
<NoteIcon />
<Title level={5} style={{ margin: 0 }}>
Notes
</Title>
</Flex>
}
key='notes' key='notes'
> >
<Card> <Card>
<DashboardNotes _id={filamentId} /> <DashboardNotes _id={filamentId} />
</Card> </Card>
</Collapse.Panel> </InfoCollapse>
</Collapse>
<Collapse <InfoCollapse
ghost title='Audit Logs'
expandIconPosition='end' icon={<AuditLogIcon />}
activeKey={collapseState.auditLogs ? ['auditLogs'] : []} active={collapseState.auditLogs}
onChange={(keys) => onToggle={(expanded) =>
updateCollapseState('auditLogs', keys.length > 0) updateCollapseState('auditLogs', expanded)
}
expandIcon={({ isActive }) => (
<CaretLeftOutlined rotate={isActive ? -90 : 0} />
)}
className='no-h-padding-collapse'
>
<Collapse.Panel
header={
<Flex align='center' gap={'middle'}>
<AuditLogIcon />
<Title level={5} style={{ margin: 0 }}>
Audit Logs
</Title>
</Flex>
} }
key='auditLogs' key='auditLogs'
> >
@ -643,11 +655,9 @@ const FilamentInfo = () => {
loading={fetchLoading} loading={fetchLoading}
showTargetColumn={false} showTargetColumn={false}
/> />
</Collapse.Panel> </InfoCollapse>
</Collapse>
</Flex> </Flex>
</div> </div>
)}
</Flex> </Flex>
</> </>
) )

View File

@ -72,7 +72,7 @@ const NewGCodeFile = ({ onOk, reset }) => {
const { token, authenticated } = useContext(AuthContext) const { token, authenticated } = useContext(AuthContext)
// eslint-disable-next-line // eslint-disable-next-line
const fetchFilamentDetails = async () => { const fetchFilamentInfo = async () => {
if (!authenticated) { if (!authenticated) {
return return
} }

View File

@ -17,7 +17,7 @@ import {
} from 'antd' } from 'antd'
import { AuthContext } from '../context/AuthContext.js' import { AuthContext } from '../context/AuthContext.js'
import { SocketContext } from '../context/SocketContext.js' import { PrintServerContext } from '../context/PrintServerContext.js'
import NewJob from './Jobs/NewJob.jsx' import NewJob from './Jobs/NewJob.jsx'
import JobState from '../common/JobState.jsx' import JobState from '../common/JobState.jsx'
import SubJobCounter from '../common/SubJobCounter.jsx' import SubJobCounter from '../common/SubJobCounter.jsx'
@ -257,7 +257,7 @@ const Jobs = () => {
] ]
const { authenticated } = useContext(AuthContext) const { authenticated } = useContext(AuthContext)
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [columnVisibility, updateColumnVisibility] = useColumnVisibility( const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
'Jobs', 'Jobs',
@ -265,9 +265,9 @@ const Jobs = () => {
) )
const handleDeployJob = (jobId) => { const handleDeployJob = (jobId) => {
if (socket) { if (printServer) {
messageApi.info(`Print job ${jobId} deployment initiated`) messageApi.info(`Print job ${jobId} deployment initiated`)
socket.emit('server.job_queue.deploy', { jobId }, (response) => { printServer.emit('server.job_queue.deploy', { jobId }, (response) => {
if (response == false) { if (response == false) {
notificationApi.error({ notificationApi.error({
message: 'Print job deployment failed', message: 'Print job deployment failed',

View File

@ -21,7 +21,7 @@ import TimeDisplay from '../../common/TimeDisplay'
import JobState from '../../common/JobState' import JobState from '../../common/JobState'
import IdText from '../../common/IdText' import IdText from '../../common/IdText'
import SubJobsTree from '../../common/SubJobsTree' import SubJobsTree from '../../common/SubJobsTree'
import { SocketContext } from '../../context/SocketContext' import { PrintServerContext } from '../../context/PrintServerContext'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon' import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import ReloadIcon from '../../../Icons/ReloadIcon' import ReloadIcon from '../../../Icons/ReloadIcon'
import useCollapseState from '../../hooks/useCollapseState' import useCollapseState from '../../hooks/useCollapseState'
@ -42,7 +42,7 @@ const JobInfo = () => {
const location = useLocation() const location = useLocation()
const [messageApi] = message.useMessage() const [messageApi] = message.useMessage()
const jobId = new URLSearchParams(location.search).get('jobId') const jobId = new URLSearchParams(location.search).get('jobId')
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [collapseState, updateCollapseState] = useCollapseState('JobInfo', { const [collapseState, updateCollapseState] = useCollapseState('JobInfo', {
info: true, info: true,
subJobs: true, subJobs: true,
@ -57,8 +57,8 @@ const JobInfo = () => {
}, [jobId]) }, [jobId])
useEffect(() => { useEffect(() => {
if (socket && jobId) { if (printServer && jobId) {
socket.on('notify_job_update', (updateData) => { printServer.on('notify_job_update', (updateData) => {
if (updateData._id === jobId) { if (updateData._id === jobId) {
setJobData((prevData) => { setJobData((prevData) => {
if (!prevData) return prevData if (!prevData) return prevData
@ -73,11 +73,11 @@ const JobInfo = () => {
} }
return () => { return () => {
if (socket) { if (printServer) {
socket.off('notify_job_update') printServer.off('notify_job_update')
} }
} }
}, [socket, jobId]) }, [printServer, jobId])
const fetchJobDetails = async () => { const fetchJobDetails = async () => {
try { try {

View File

@ -22,7 +22,7 @@ import {
} from 'antd' } from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import { SocketContext } from '../../context/SocketContext' import { PrintServerContext } from '../../context/PrintServerContext'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel' import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
import PrinterPositionPanel from '../../common/PrinterPositionPanel' import PrinterPositionPanel from '../../common/PrinterPositionPanel'
@ -106,7 +106,7 @@ const ControlPrinter = () => {
) )
}, [componentVisibility]) }, [componentVisibility])
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const { authenticated } = useContext(AuthContext) const { authenticated } = useContext(AuthContext)
// Fetch printer details when the component mounts // Fetch printer details when the component mounts
@ -143,9 +143,9 @@ const ControlPrinter = () => {
// Add WebSocket event listener for real-time updates // Add WebSocket event listener for real-time updates
useEffect(() => { useEffect(() => {
if (socket && !initialized && printerId) { if (printServer && !initialized && printerId) {
setInitialized(true) setInitialized(true)
socket.on('notify_printer_update', (statusUpdate) => { printServer.on('notify_printer_update', (statusUpdate) => {
setPrinterData((prevData) => { setPrinterData((prevData) => {
if (statusUpdate?._id === printerId) { if (statusUpdate?._id === printerId) {
return { return {
@ -158,7 +158,7 @@ const ControlPrinter = () => {
}) })
// Add WebSocket event listener for filament stock updates // Add WebSocket event listener for filament stock updates
socket.on('notify_filamentstock_update', (filamentStockUpdate) => { printServer.on('notify_filamentstock_update', (filamentStockUpdate) => {
setPrinterData((prevData) => { setPrinterData((prevData) => {
if (prevData?.currentFilamentStock) { if (prevData?.currentFilamentStock) {
if ( if (
@ -178,17 +178,17 @@ const ControlPrinter = () => {
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('Deregistering') console.log('Deregistering')
socket.off('notify_printer_update') printServer.off('notify_printer_update')
socket.off('notify_filamentstock_update') printServer.off('notify_filamentstock_update')
} }
} }
}, [socket, initialized, printerId]) }, [printServer, initialized, printerId])
function handleEmergencyStop() { function handleEmergencyStop() {
console.log('Emergency stop button clicked') console.log('Emergency stop button clicked')
socket.emit('printer.emergency_stop', { printerId }) printServer.emit('printer.emergency_stop', { printerId })
} }
useEffect(() => { useEffect(() => {
@ -328,19 +328,19 @@ const ControlPrinter = () => {
], ],
onClick: ({ key }) => { onClick: ({ key }) => {
if (key === 'restartHost') { if (key === 'restartHost') {
socket.emit('printer.restart', { printerId }) printServer.emit('printer.restart', { printerId })
} else if (key === 'restartFirmware') { } else if (key === 'restartFirmware') {
socket.emit('printer.firmware_restart', { printerId }) printServer.emit('printer.firmware_restart', { printerId })
} else if (key === 'resumePrint') { } else if (key === 'resumePrint') {
socket.emit('printer.print.resume', { printerId }) printServer.emit('printer.print.resume', { printerId })
} else if (key === 'pausePrint') { } else if (key === 'pausePrint') {
socket.emit('printer.print.pause', { printerId }) printServer.emit('printer.print.pause', { printerId })
} else if (key === 'cancelPrint') { } else if (key === 'cancelPrint') {
socket.emit('printer.print.cancel', { printerId }) printServer.emit('printer.print.cancel', { printerId })
} else if (key === 'startQueue') { } else if (key === 'startQueue') {
socket.emit('server.job_queue.start', { printerId }) printServer.emit('server.job_queue.start', { printerId })
} else if (key === 'pauseQueue') { } else if (key === 'pauseQueue') {
socket.emit('server.job_queue.pause', { printerId }) printServer.emit('server.job_queue.pause', { printerId })
} else if (key === 'loadFilamentStock') { } else if (key === 'loadFilamentStock') {
setLoadFilamentStockModalOpen(true) setLoadFilamentStockModalOpen(true)
} else if (key === 'unloadFilamentStock') { } else if (key === 'unloadFilamentStock') {
@ -467,9 +467,9 @@ const ControlPrinter = () => {
} }
onClick={() => { onClick={() => {
if (printerData?.state?.type === 'paused') { if (printerData?.state?.type === 'paused') {
socket.emit('printer.print.resume', { printerId }) printServer.emit('printer.print.resume', { printerId })
} else { } else {
socket.emit('printer.print.pause', { printerId }) printServer.emit('printer.print.pause', { printerId })
} }
}} }}
></Button> ></Button>
@ -482,7 +482,7 @@ const ControlPrinter = () => {
printerData?.state?.type === 'error' printerData?.state?.type === 'error'
} }
onClick={() => { onClick={() => {
socket.emit('server.job_queue.start', { printerId }) printServer.emit('server.job_queue.start', { printerId })
}} }}
></Button> ></Button>
</Space> </Space>
@ -933,7 +933,7 @@ const ControlPrinter = () => {
key='firmwareRestart' key='firmwareRestart'
icon={<ReloadIcon />} icon={<ReloadIcon />}
onClick={() => { onClick={() => {
socket.emit('printer.firmware_restart', { printerId }) printServer.emit('printer.firmware_restart', { printerId })
setKlippyErrorModalOpen(false) setKlippyErrorModalOpen(false)
}} }}
> >

View File

@ -21,7 +21,7 @@ import {
} from 'antd' } from 'antd'
import { SearchOutlined, SettingOutlined } from '@ant-design/icons' import { SearchOutlined, SettingOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { SocketContext } from '../../context/SocketContext' import { PrintServerContext } from '../../context/PrintServerContext'
import EditIcon from '../../../Icons/EditIcon.jsx' import EditIcon from '../../../Icons/EditIcon.jsx'
import config from '../../../../config.js' import config from '../../../../config.js'
@ -43,7 +43,7 @@ const NewPrinter = ({ onOk, reset }) => {
reset: PropTypes.bool.isRequired reset: PropTypes.bool.isRequired
} }
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const [notificationApi, notificationContextHolder] = const [notificationApi, notificationContextHolder] =
notification.useNotification() notification.useNotification()
@ -243,22 +243,22 @@ const NewPrinter = ({ onOk, reset }) => {
setDiscovering(true) setDiscovering(true)
setDiscoveredPrinters([]) setDiscoveredPrinters([])
messageApi.info('Discovering printers...') messageApi.info('Discovering printers...')
socket.off('notify_scan_network_found') printServer.off('notify_scan_network_found')
socket.off('notify_scan_network_progress') printServer.off('notify_scan_network_progress')
socket.off('notify_scan_network_complete') printServer.off('notify_scan_network_complete')
socket.on('notify_scan_network_found', notifyScanNetworkFound) printServer.on('notify_scan_network_found', notifyScanNetworkFound)
socket.on('notify_scan_network_progress', notifyScanNetworkProgress) printServer.on('notify_scan_network_progress', notifyScanNetworkProgress)
socket.on('notify_scan_network_complete', notifyScanNetworkComplete) printServer.on('notify_scan_network_complete', notifyScanNetworkComplete)
socket.emit('bridge.scan_network.start', { printServer.emit('bridge.scan_network.start', {
port: scanPort, port: scanPort,
protocol: scanProtocol protocol: scanProtocol
}) })
} }
}, [ }, [
discovering, discovering,
socket, printServer,
scanPort, scanPort,
scanProtocol, scanProtocol,
messageApi, messageApi,
@ -279,10 +279,10 @@ const NewPrinter = ({ onOk, reset }) => {
setDiscovering(false) setDiscovering(false)
notificationApi.destroy('network-scan') notificationApi.destroy('network-scan')
messageApi.info('Stopping discovery...') messageApi.info('Stopping discovery...')
socket.off('notify_scan_network_found') printServer.off('notify_scan_network_found')
socket.off('notify_scan_network_progress') printServer.off('notify_scan_network_progress')
socket.off('notify_scan_network_complete') printServer.off('notify_scan_network_complete')
socket.emit('bridge.scan_network.stop', (response) => { printServer.emit('bridge.scan_network.stop', (response) => {
if (response == false) { if (response == false) {
messageApi.error('Error stopping discovery!') messageApi.error('Error stopping discovery!')
} }

View File

@ -46,7 +46,7 @@ const breadcrumbNameMap = {
'/dashboard/inventory/stockaudits/info': 'Info', '/dashboard/inventory/stockaudits/info': 'Info',
'/dashboard/developer/sessionstorage': 'Session Storage', '/dashboard/developer/sessionstorage': 'Session Storage',
'/dashboard/developer/authcontextdebug': 'Auth Context Debug', '/dashboard/developer/authcontextdebug': 'Auth Context Debug',
'/dashboard/developer/socketcontextdebug': 'Socket Context Debug' '/dashboard/developer/printservercontextdebug': 'Print Server Context Debug'
} }
const DashboardBreadcrumb = () => { const DashboardBreadcrumb = () => {

View File

@ -8,9 +8,8 @@ import {
Dropdown, Dropdown,
Button, Button,
Tooltip, Tooltip,
Typography, Badge,
Divider, Divider
Badge
} from 'antd' } from 'antd'
import { import {
LogoutOutlined, LogoutOutlined,
@ -19,12 +18,13 @@ import {
LoadingOutlined LoadingOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { AuthContext } from '../context/AuthContext' import { AuthContext } from '../context/AuthContext'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import { SpotlightContext } from '../context/SpotlightContext' import { SpotlightContext } from '../context/SpotlightContext'
import { NotificationContext } from '../context/NotificationContext' import { ApiServerContext } from '../context/ApiServerContext'
import { useNavigate, useLocation } from 'react-router-dom' import { useNavigate, useLocation } from 'react-router-dom'
import { Header } from 'antd/es/layout/layout' import { Header } from 'antd/es/layout/layout'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import KeyboardShortcut from './KeyboardShortcut'
import FarmControlLogo from '../../Logos/FarmControlLogo' import FarmControlLogo from '../../Logos/FarmControlLogo'
import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall' import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
@ -36,16 +36,16 @@ import BellIcon from '../../Icons/BellIcon'
import SearchIcon from '../../Icons/SearchIcon' import SearchIcon from '../../Icons/SearchIcon'
import SettingsIcon from '../../Icons/SettingsIcon' import SettingsIcon from '../../Icons/SettingsIcon'
import DeveloperIcon from '../../Icons/DeveloperIcon' import DeveloperIcon from '../../Icons/DeveloperIcon'
import PrinterIcon from '../../Icons/PrinterIcon'
const { Text } = Typography
const DashboardNavigation = () => { const DashboardNavigation = () => {
const { logout, userProfile } = useContext(AuthContext) const { logout, userProfile } = useContext(AuthContext)
const { showSpotlight } = useContext(SpotlightContext) const { showSpotlight } = useContext(SpotlightContext)
const { toggleNotificationCenter, unreadCount } = const { toggleNotificationCenter, unreadCount } = useContext(ApiServerContext)
useContext(NotificationContext) const { printServer } = useContext(PrintServerContext)
const { socket } = useContext(SocketContext) const { apiServer } = useContext(ApiServerContext)
const [socketState, setSocketState] = useState('disconnected') const [printServerState, setPrintServerState] = useState('disconnected')
const [apiServerState, setApiServerState] = useState('disconnected')
const navigate = useNavigate() const navigate = useNavigate()
const location = useLocation() const location = useLocation()
const [selectedKey, setSelectedKey] = useState('production') const [selectedKey, setSelectedKey] = useState('production')
@ -59,14 +59,24 @@ const DashboardNavigation = () => {
}, [location.pathname]) }, [location.pathname])
useEffect(() => { useEffect(() => {
if (socket?.connecting) { if (printServer?.connecting) {
setSocketState('connecting') setPrintServerState('connecting')
} else if (socket?.connected) { } else if (printServer?.connected) {
setSocketState('connected') setPrintServerState('connected')
} else { } else {
setSocketState('disconnected') setPrintServerState('disconnected')
} }
}, [socket?.connecting, socket?.connected]) }, [printServer?.connecting, printServer?.connected])
useEffect(() => {
if (apiServer?.connecting) {
setApiServerState('connecting')
} else if (apiServer?.connected) {
setApiServerState('connected')
} else {
setApiServerState('disconnected')
}
}, [apiServer?.connecting, apiServer?.connected])
const mainMenuItems = [ const mainMenuItems = [
{ {
@ -165,35 +175,47 @@ const DashboardNavigation = () => {
/> />
<Flex gap={'middle'} align='center'> <Flex gap={'middle'} align='center'>
<Space> <Space>
<Tooltip title={<Text keyboard> P</Text>} arrow={false}> <KeyboardShortcut
shortcut='alt+shift+q'
hint='⌘ ⇧ P'
onTrigger={() => showSpotlight()}
>
<Button <Button
icon={<SearchIcon />} icon={<SearchIcon />}
type='text' type='text'
style={{ marginTop: '2px' }} style={{ marginTop: '2px' }}
onClick={() => showSpotlight()} onClick={() => showSpotlight()}
></Button> />
</Tooltip> </KeyboardShortcut>
<Badge count={unreadCount} size='small'> <Badge count={unreadCount} size='small'>
<KeyboardShortcut
shortcut='alt+n'
hint='ALT N'
onTrigger={() => toggleNotificationCenter()}
>
<Button <Button
icon={<BellIcon />} icon={<BellIcon />}
type='text' type='text'
style={{ marginTop: '2px' }} style={{ marginTop: '2px' }}
onClick={toggleNotificationCenter} onClick={() => toggleNotificationCenter()}
></Button> />
</KeyboardShortcut>
</Badge> </Badge>
</Space> </Space>
{process.env.NODE_ENV === 'development' && (
<Space> <Space>
{socketState === 'connected' ? ( {printServerState === 'connected' ? (
<Tooltip title='Connected to server' arrow={false}> <Tooltip title='Connected to print server' arrow={false}>
<Tag <Tag
color='success' color='success'
style={{ marginRight: 0 }} style={{ marginRight: 0 }}
icon={<CloudIcon />} icon={<PrinterIcon />}
/> />
</Tooltip> </Tooltip>
) : null} ) : null}
{socketState === 'connecting' ? ( {printServerState === 'connecting' ? (
<Tooltip title='Connecting to server...' arrow={false}> <Tooltip title='Connecting to print erver...' arrow={false}>
<Tag <Tag
color='default' color='default'
style={{ marginRight: 0 }} style={{ marginRight: 0 }}
@ -201,8 +223,35 @@ const DashboardNavigation = () => {
/> />
</Tooltip> </Tooltip>
) : null} ) : null}
{socketState === 'disconnected' ? ( {printServerState === 'disconnected' ? (
<Tooltip title='Disconnected from server' arrow={false}> <Tooltip title='Disconnected from print server' arrow={false}>
<Tag
color='error'
style={{ marginRight: 0 }}
icon={<PrinterIcon />}
/>
</Tooltip>
) : null}
{apiServerState === 'connected' ? (
<Tooltip title='Connected to api server' arrow={false}>
<Tag
color='success'
style={{ marginRight: 0 }}
icon={<CloudIcon />}
/>
</Tooltip>
) : null}
{apiServerState === 'connecting' ? (
<Tooltip title='Connecting to api erver...' arrow={false}>
<Tag
color='default'
style={{ marginRight: 0 }}
icon={<LoadingOutlined />}
/>
</Tooltip>
) : null}
{apiServerState === 'disconnected' ? (
<Tooltip title='Disconnected from api server' arrow={false}>
<Tag <Tag
color='error' color='error'
style={{ marginRight: 0 }} style={{ marginRight: 0 }}
@ -210,9 +259,6 @@ const DashboardNavigation = () => {
/> />
</Tooltip> </Tooltip>
) : null} ) : null}
</Space>
{process.env.NODE_ENV === 'development' && (
<Space>
<Tooltip title='Developer' arrow={false}> <Tooltip title='Developer' arrow={false}>
<Tag <Tag
color='yellow' color='yellow'

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types'
import { Badge, Progress, Flex, Space, Tag, Typography } from 'antd' import { Badge, Progress, Flex, Space, Tag, Typography } from 'antd'
import { green } from '@ant-design/colors' import { green } from '@ant-design/colors'
import React, { useState, useContext, useEffect } from 'react' import React, { useState, useContext, useEffect } from 'react'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
const { Text } = Typography const { Text } = Typography
@ -25,7 +25,7 @@ const FilamentStockState = ({
showProgress = true, showProgress = true,
showStatus = true showStatus = true
}) => { }) => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [badgeStatus, setBadgeStatus] = useState('unknown') const [badgeStatus, setBadgeStatus] = useState('unknown')
const [badgeText, setBadgeText] = useState('Unknown') const [badgeText, setBadgeText] = useState('Unknown')
const [currentState, setCurrentState] = useState( const [currentState, setCurrentState] = useState(
@ -37,20 +37,20 @@ const FilamentStockState = ({
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
useEffect(() => { useEffect(() => {
if (socket && !initialized && filamentStock?._id) { if (printServer && !initialized && filamentStock?._id) {
setInitialized(true) setInitialized(true)
socket.on('notify_filamentstock_update', (statusUpdate) => { printServer.on('notify_filamentstock_update', (statusUpdate) => {
if (statusUpdate?._id === filamentStock?._id && statusUpdate?.state) { if (statusUpdate?._id === filamentStock?._id && statusUpdate?.state) {
setCurrentState(statusUpdate.state) setCurrentState(statusUpdate.state)
} }
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
socket.off('notify_filamentstock_update') printServer.off('notify_filamentstock_update')
} }
} }
}, [socket, initialized, filamentStock?._id]) }, [printServer, initialized, filamentStock?._id])
useEffect(() => { useEffect(() => {
switch (currentState.type) { switch (currentState.type) {

View File

@ -0,0 +1,55 @@
import React from 'react'
import { Collapse, Flex, Typography } from 'antd'
import { CaretLeftOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
const { Title } = Typography
const InfoCollapse = ({
title,
icon,
children,
active,
onToggle,
className = '',
key = 'default'
}) => {
return (
<Collapse
ghost
expandIconPosition='end'
activeKey={active ? [key] : []}
onChange={(keys) => onToggle(keys.length > 0)}
expandIcon={({ isActive }) => (
<CaretLeftOutlined rotate={isActive ? -90 : 0} />
)}
className={`no-h-padding-collapse ${className}`}
>
<Collapse.Panel
header={
<Flex align='center' gap={'middle'}>
{icon}
<Title level={5} style={{ margin: 0 }}>
{title}
</Title>
</Flex>
}
key={key}
>
{children}
</Collapse.Panel>
</Collapse>
)
}
InfoCollapse.propTypes = {
title: PropTypes.string.isRequired,
icon: PropTypes.node.isRequired,
children: PropTypes.node.isRequired,
active: PropTypes.bool.isRequired,
onToggle: PropTypes.func.isRequired,
className: PropTypes.string,
key: PropTypes.string
}
export default InfoCollapse

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Progress, Flex, Typography, Space } from 'antd' import { Progress, Flex, Typography, Space } from 'antd'
import React, { useState, useContext, useEffect } from 'react' import React, { useState, useContext, useEffect } from 'react'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import IdText from './IdText' import IdText from './IdText'
import StateTag from './StateTag' import StateTag from './StateTag'
@ -12,7 +12,7 @@ const JobState = ({
showId = true, showId = true,
showQuantity = true showQuantity = true
}) => { }) => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [currentState, setCurrentState] = useState( const [currentState, setCurrentState] = useState(
job?.state || { type: 'unknown', progress: 0 } job?.state || { type: 'unknown', progress: 0 }
) )
@ -20,20 +20,20 @@ const JobState = ({
const { Text } = Typography const { Text } = Typography
useEffect(() => { useEffect(() => {
if (socket && !initialized && job?._id) { if (printServer && !initialized && job?._id) {
setInitialized(true) setInitialized(true)
socket.on('notify_job_update', (statusUpdate) => { printServer.on('notify_job_update', (statusUpdate) => {
if (statusUpdate?._id === job._id && statusUpdate?.state) { if (statusUpdate?._id === job._id && statusUpdate?.state) {
setCurrentState(statusUpdate.state) setCurrentState(statusUpdate.state)
} }
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
socket.off('notify_job_update') printServer.off('notify_job_update')
} }
} }
}, [socket, initialized, job?._id]) }, [printServer, initialized, job?._id])
return ( return (
<Flex gap='small' align={'center'}> <Flex gap='small' align={'center'}>

View File

@ -0,0 +1,115 @@
import React, { useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import { Popover, Typography } from 'antd'
// Utility to parse shortcut string like 'cmd+shift+p' or 'ctrl+s'
function parseShortcut(shortcut) {
const parts = shortcut.toLowerCase().split('+')
return {
meta: parts.includes('cmd') || parts.includes('meta') || false,
ctrl: parts.includes('ctrl') || parts.includes('control') || false,
alt: parts.includes('alt') || parts.includes('option') || false,
shift: parts.includes('shift') || false,
key: parts.find(
(p) =>
!['cmd', 'meta', 'ctrl', 'control', 'alt', 'option', 'shift'].includes(
p
)
)[0]
}
}
const { Text } = Typography
const KeyboardShortcut = ({ shortcut, children, hint, onTrigger }) => {
const childRef = useRef()
const shortcutObj = parseShortcut(shortcut)
let pressedKeys = new Set()
// Helper to get the set of keys required for the shortcut
function getShortcutKeySet(shortcutObj) {
const keys = []
if (shortcutObj.meta) keys.push('Meta')
if (shortcutObj.ctrl) keys.push('Control')
if (shortcutObj.alt) keys.push('Alt')
if (shortcutObj.shift) keys.push('Shift')
// shortcutObj.code is like 'keyp', so extract the last char
if (shortcutObj.key) {
keys.push('Key' + shortcutObj.key.toUpperCase())
}
return new Set(keys)
}
const shortcutKeySet = getShortcutKeySet(shortcutObj)
useEffect(() => {
pressedKeys = new Set()
const handleKeyDown = (event) => {
if (
event.key === 'Meta' ||
event.key === 'Control' ||
event.key === 'Alt' ||
event.key === 'Shift'
) {
pressedKeys.add(event.key)
} else if (event.code && event.code.startsWith('Key')) {
pressedKeys.add(event.code)
}
if (
shortcutKeySet.size &&
[...shortcutKeySet].every((k) => pressedKeys.has(k))
) {
if (typeof onTrigger === 'function') {
onTrigger(event)
}
event.preventDefault()
}
}
const handleKeyUp = (event) => {
if (
event.key === 'Meta' ||
event.key === 'Control' ||
event.key === 'Alt' ||
event.key === 'Shift'
) {
pressedKeys.delete(event.key)
} else if (event.code && event.code.startsWith('Key')) {
pressedKeys.delete(event.key.toUpperCase())
}
}
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
return () => {
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
}
}, [shortcut, shortcutObj, onTrigger])
// Clone the child to attach a ref
const element = React.cloneElement(children, { ref: childRef })
if (hint) {
return (
<Popover
content={
<Text keyboard className='keyboard-shortcut-tooltip'>
{hint}
</Text>
}
arrow={false}
>
{element}
</Popover>
)
}
return element
}
KeyboardShortcut.propTypes = {
shortcut: PropTypes.string.isRequired, // e.g. 'cmd+shift+p'
onTrigger: PropTypes.func.isRequired,
children: PropTypes.element.isRequired,
hint: PropTypes.string // Optional, e.g. ' P'
}
export default KeyboardShortcut

View File

@ -4,7 +4,7 @@ import { LoadingOutlined } from '@ant-design/icons'
import React, { useState, useEffect, useContext } from 'react' import React, { useState, useEffect, useContext } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import SubJobState from './SubJobState' import SubJobState from './SubJobState'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import axios from 'axios' import axios from 'axios'
import JobState from './JobState' import JobState from './JobState'
import JobIcon from '../../Icons/JobIcon' import JobIcon from '../../Icons/JobIcon'
@ -20,7 +20,7 @@ const PrinterJobsTree = ({
const [subJobs, setSubJobs] = useState(initialSubJobs || []) const [subJobs, setSubJobs] = useState(initialSubJobs || [])
const [treeLoading, setTreeLoading] = useState(initialLoading) const [treeLoading, setTreeLoading] = useState(initialLoading)
const [error, setError] = useState(null) const [error, setError] = useState(null)
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [messageApi] = message.useMessage() const [messageApi] = message.useMessage()
const [expandedKeys, setExpandedKeys] = useState([]) const [expandedKeys, setExpandedKeys] = useState([])
const [treeData, setTreeData] = useState([]) const [treeData, setTreeData] = useState([])
@ -116,9 +116,9 @@ const PrinterJobsTree = ({
initializeData() initializeData()
// Add socket.io event listener for subjob updates // Add printServer.io event listener for subjob updates
if (socket) { if (printServer) {
socket.on('notify_subjob_update', (updateData) => { printServer.on('notify_subjob_update', (updateData) => {
if (updateData.subJobId) { if (updateData.subJobId) {
setSubJobs((prevSubJobs) => setSubJobs((prevSubJobs) =>
prevSubJobs.map((subJob) => { prevSubJobs.map((subJob) => {
@ -137,11 +137,11 @@ const PrinterJobsTree = ({
} }
return () => { return () => {
if (socket) { if (printServer) {
socket.off('notify_subjob_update') printServer.off('notify_subjob_update')
} }
} }
}, [initialSubJobs, socket]) }, [initialSubJobs, printServer])
if (error) { if (error) {
return ( return (

View File

@ -1,7 +1,7 @@
import React, { useContext, useState, useEffect } from 'react' import React, { useContext, useState, useEffect } from 'react'
import { Typography, Spin, Flex, Space, Slider, Descriptions, Tag } from 'antd' import { Typography, Spin, Flex, Space, Slider, Descriptions, Tag } from 'antd'
import { LoadingOutlined } from '@ant-design/icons' import { LoadingOutlined } from '@ant-design/icons'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
const { Text } = Typography const { Text } = Typography
@ -33,7 +33,7 @@ const PrinterMiscPanel = ({
}) })
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [fanSpeed, setFanSpeed] = useState(0) const [fanSpeed, setFanSpeed] = useState(0)
const [ledBrightness, setLedBrightness] = useState(0) const [ledBrightness, setLedBrightness] = useState(0)
const [beeperValue, setBeeperValue] = useState(0) const [beeperValue, setBeeperValue] = useState(0)
@ -89,30 +89,30 @@ const PrinterMiscPanel = ({
} }
} }
if (!initialized && socket.connected) { if (!initialized && printServer.connected) {
setInitialized(true) setInitialized(true)
socket.on('connect', () => { printServer.on('connect', () => {
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
}) })
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
socket.on('notify_status_update', notifyMiscStatusUpdate) printServer.on('notify_status_update', notifyMiscStatusUpdate)
} }
return () => { return () => {
if (socket.connected && initialized && shouldUnsubscribe) { if (printServer.connected && initialized && shouldUnsubscribe) {
socket.off('notify_status_update', notifyMiscStatusUpdate) printServer.off('notify_status_update', notifyMiscStatusUpdate)
socket.emit('printer.objects.unsubscribe', params) printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [socket, initialized, printerId, shouldUnsubscribe]) }, [printServer, initialized, printerId, shouldUnsubscribe])
const handleSetFanSpeed = (value) => { const handleSetFanSpeed = (value) => {
if (socket) { if (printServer) {
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `M106 S${Math.round(value * 255)}` script: `M106 S${Math.round(value * 255)}`
}) })
@ -120,8 +120,8 @@ const PrinterMiscPanel = ({
} }
const handleSetLedBrightness = (value) => { const handleSetLedBrightness = (value) => {
if (socket) { if (printServer) {
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `SET_LED LED=led_backlight BRIGHTNESS=${value}` script: `SET_LED LED=led_backlight BRIGHTNESS=${value}`
}) })
@ -129,8 +129,8 @@ const PrinterMiscPanel = ({
} }
const handleSetBeeperValue = (value) => { const handleSetBeeperValue = (value) => {
if (socket) { if (printServer) {
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `M300 S440 P200 V${Math.round(value * 100)}` script: `M300 S440 P200 V${Math.round(value * 100)}`
}) })

View File

@ -10,7 +10,7 @@ import {
Card, Card,
message // eslint-disable-line message // eslint-disable-line
} from 'antd' } from 'antd'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import LevelBedIcon from '../../Icons/LevelBedIcon' import LevelBedIcon from '../../Icons/LevelBedIcon'
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon' import ArrowLeftIcon from '../../Icons/ArrowLeftIcon'
@ -22,7 +22,7 @@ import HomeIcon from '../../Icons/HomeIcon'
const PrinterMovementPanel = ({ printerId }) => { const PrinterMovementPanel = ({ printerId }) => {
const [posValue, setPosValue] = useState(10) const [posValue, setPosValue] = useState(10)
const [rateValue, setRateValue] = useState(1000) const [rateValue, setRateValue] = useState(1000)
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
//const messageApi = message.useMessage() //const messageApi = message.useMessage()
@ -40,9 +40,9 @@ const PrinterMovementPanel = ({ printerId }) => {
} }
const handleHomeAxisClick = (axis) => { const handleHomeAxisClick = (axis) => {
if (socket) { if (printServer) {
console.log('Homeing Axis:', axis) console.log('Homeing Axis:', axis)
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `G28 ${axis}` script: `G28 ${axis}`
}) })
@ -51,9 +51,9 @@ const PrinterMovementPanel = ({ printerId }) => {
const handleMoveAxisClick = (axis, minus) => { const handleMoveAxisClick = (axis, minus) => {
const distanceValue = !minus ? posValue * -1 : posValue const distanceValue = !minus ? posValue * -1 : posValue
if (socket) { if (printServer) {
console.log('Moving Axis:', axis, distanceValue) console.log('Moving Axis:', axis, distanceValue)
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `_CLIENT_LINEAR_MOVE ${axis}=${distanceValue} F=${rateValue}` script: `_CLIENT_LINEAR_MOVE ${axis}=${distanceValue} F=${rateValue}`
}) })

View File

@ -10,7 +10,7 @@ import {
Button Button
} from 'antd' } from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import styled from 'styled-components' import styled from 'styled-components'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import BoolDisplay from './BoolDisplay' import BoolDisplay from './BoolDisplay'
@ -46,7 +46,7 @@ const PrinterPositionPanel = ({
}) })
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [speedFactor, setSpeedFactor] = useState(positionData.speed_factor) const [speedFactor, setSpeedFactor] = useState(positionData.speed_factor)
const [extrudeFactor, setExtrudeFactor] = useState( const [extrudeFactor, setExtrudeFactor] = useState(
positionData.extrude_factor positionData.extrude_factor
@ -76,33 +76,33 @@ const PrinterPositionPanel = ({
} }
} }
if (!initialized && socket?.connected) { if (!initialized && printServer?.connected) {
setInitialized(true) setInitialized(true)
socket.on('connect', () => { printServer.on('connect', () => {
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
}) })
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
socket.on('notify_status_update', notifyPositionStatusUpdate) printServer.on('notify_status_update', notifyPositionStatusUpdate)
} }
setSpeedFactor(positionData.speed_factor) setSpeedFactor(positionData.speed_factor)
setExtrudeFactor(positionData.extrude_factor) setExtrudeFactor(positionData.extrude_factor)
return () => { return () => {
if (socket?.connected && initialized && shouldUnsubscribe) { if (printServer?.connected && initialized && shouldUnsubscribe) {
socket.off('notify_status_update', notifyPositionStatusUpdate) printServer.off('notify_status_update', notifyPositionStatusUpdate)
socket.emit('printer.objects.unsubscribe', params) printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [socket, initialized, printerId, shouldUnsubscribe]) }, [printServer, initialized, printerId, shouldUnsubscribe])
const handleSetSpeedFactor = () => { const handleSetSpeedFactor = () => {
if (socket) { if (printServer) {
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `M220 S${speedFactor * 100}` script: `M220 S${speedFactor * 100}`
}) })
@ -110,8 +110,8 @@ const PrinterPositionPanel = ({
} }
const handleSetExtrudeFactor = () => { const handleSetExtrudeFactor = () => {
if (socket) { if (printServer) {
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `M221 S${extrudeFactor * 100}` script: `M221 S${extrudeFactor * 100}`
}) })

View File

@ -2,7 +2,7 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Progress, Flex, Space, Typography, Button, Tooltip } from 'antd' import { Progress, Flex, Space, Typography, Button, Tooltip } from 'antd'
import React, { useState, useContext, useEffect } from 'react' import React, { useState, useContext, useEffect } from 'react'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import { CaretLeftOutlined } from '@ant-design/icons' import { CaretLeftOutlined } from '@ant-design/icons'
import XMarkIcon from '../../Icons/XMarkIcon' import XMarkIcon from '../../Icons/XMarkIcon'
import PauseIcon from '../../Icons/PauseIcon' import PauseIcon from '../../Icons/PauseIcon'
@ -15,7 +15,7 @@ const PrinterState = ({
showPrinterName = true, showPrinterName = true,
showControls = true showControls = true
}) => { }) => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [currentState, setCurrentState] = useState( const [currentState, setCurrentState] = useState(
printer?.state || { printer?.state || {
type: 'unknown', type: 'unknown',
@ -26,20 +26,20 @@ const PrinterState = ({
const { Text } = Typography const { Text } = Typography
useEffect(() => { useEffect(() => {
if (socket && !initialized && printer?.id) { if (printServer && !initialized && printer?.id) {
setInitialized(true) setInitialized(true)
socket.on('notify_printer_update', (statusUpdate) => { printServer.on('notify_printer_update', (statusUpdate) => {
if (statusUpdate?._id === printer.id && statusUpdate?.state) { if (statusUpdate?._id === printer.id && statusUpdate?.state) {
setCurrentState(statusUpdate.state) setCurrentState(statusUpdate.state)
} }
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
socket.off('notify_printer_update') printServer.off('notify_printer_update')
} }
} }
}, [socket, initialized, printer?.id]) }, [printServer, initialized, printer?.id])
return ( return (
<Flex gap='small' align={'center'}> <Flex gap='small' align={'center'}>
@ -67,11 +67,11 @@ const PrinterState = ({
<Button <Button
onClick={() => { onClick={() => {
if (currentState.type === 'printing') { if (currentState.type === 'printing') {
socket.emit('printer.print.pause', { printServer.emit('printer.print.pause', {
printerId: printer.id printerId: printer.id
}) })
} else { } else {
socket.emit('printer.print.resume', { printServer.emit('printer.print.resume', {
printerId: printer.id printerId: printer.id
}) })
} }
@ -94,7 +94,7 @@ const PrinterState = ({
<Tooltip title='Cancel' arrow={false}> <Tooltip title='Cancel' arrow={false}>
<Button <Button
onClick={() => { onClick={() => {
socket.emit('printer.print.cancel', { printServer.emit('printer.print.cancel', {
printerId: printer.id printerId: printer.id
}) })
}} }}

View File

@ -11,7 +11,7 @@ import {
Button Button
} from 'antd' } from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons' import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import styled from 'styled-components' import styled from 'styled-components'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
@ -50,7 +50,7 @@ const PrinterTemperaturePanel = ({
const [heatedBedTemperature, setHeatedBedTemperature] = useState( const [heatedBedTemperature, setHeatedBedTemperature] = useState(
temperatureData?.heatedBed?.target || 0 temperatureData?.heatedBed?.target || 0
) )
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const notifyTemperatureStatusUpdate = useCallback((statusUpdate) => { const notifyTemperatureStatusUpdate = useCallback((statusUpdate) => {
setTemperatureData((prev) => { setTemperatureData((prev) => {
@ -96,25 +96,25 @@ const PrinterTemperaturePanel = ({
heater_bed: null // eslint-disable-line heater_bed: null // eslint-disable-line
} }
} }
if (socket?.connected == true) { if (printServer?.connected == true) {
console.log('Printer Temperature Panel is subscribing...') console.log('Printer Temperature Panel is subscribing...')
socket.emit('printer.objects.subscribe', params) printServer.emit('printer.objects.subscribe', params)
socket.emit('printer.objects.query', params) printServer.emit('printer.objects.query', params)
socket.on('notify_status_update', notifyTemperatureStatusUpdate) printServer.on('notify_status_update', notifyTemperatureStatusUpdate)
} }
return () => { return () => {
if (socket && shouldUnsubscribe == true) { if (printServer && shouldUnsubscribe == true) {
console.log('Printer Temperature Panel is unsubscribing...') console.log('Printer Temperature Panel is unsubscribing...')
socket.off('notify_status_update', notifyTemperatureStatusUpdate) printServer.off('notify_status_update', notifyTemperatureStatusUpdate)
socket.emit('printer.objects.unsubscribe', params) printServer.emit('printer.objects.unsubscribe', params)
} }
} }
}, [socket, printerId, notifyTemperatureStatusUpdate, shouldUnsubscribe]) }, [printServer, printerId, notifyTemperatureStatusUpdate, shouldUnsubscribe])
const handleSetTemperatureClick = (target, value) => { const handleSetTemperatureClick = (target, value) => {
if (socket) { if (printServer) {
console.log('printer.gcode.script', target, value) console.log('printer.gcode.script', target, value)
socket.emit('printer.gcode.script', { printServer.emit('printer.gcode.script', {
printerId, printerId,
script: `SET_HEATER_TEMPERATURE HEATER=${target} TARGET=${value}` script: `SET_HEATER_TEMPERATURE HEATER=${target} TARGET=${value}`
}) })

View File

@ -3,7 +3,7 @@ import { Table, Typography } from 'antd'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import IdText from './IdText' import IdText from './IdText'
import { AuditOutlined } from '@ant-design/icons' import { AuditOutlined } from '@ant-design/icons'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import moment from 'moment' import moment from 'moment'
import TimeDisplay from '../common/TimeDisplay' import TimeDisplay from '../common/TimeDisplay'
import PlusMinusIcon from '../../Icons/PlusMinusIcon' import PlusMinusIcon from '../../Icons/PlusMinusIcon'
@ -13,15 +13,15 @@ import PlayCircleIcon from '../../Icons/PlayCircleIcon'
const { Text } = Typography const { Text } = Typography
const StockEventTable = ({ stockEvents }) => { const StockEventTable = ({ stockEvents }) => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
const [stockEventsData, setStockEventsData] = useState(stockEvents) const [stockEventsData, setStockEventsData] = useState(stockEvents)
useEffect(() => { useEffect(() => {
// Add WebSocket event listener for real-time updates // Add WebSocket event listener for real-time updates
if (socket && !initialized) { if (printServer && !initialized) {
setInitialized(true) setInitialized(true)
socket.on('notify_stockevent_update', (updateData) => { printServer.on('notify_stockevent_update', (updateData) => {
console.log('Received stock event update:', updateData) console.log('Received stock event update:', updateData)
setStockEventsData((prevData) => { setStockEventsData((prevData) => {
return prevData.map((stockEvent) => { return prevData.map((stockEvent) => {
@ -41,12 +41,12 @@ const StockEventTable = ({ stockEvents }) => {
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('Deregistering stock event update listener') console.log('Deregistering stock event update listener')
socket.off('notify_stockevent_update') printServer.off('notify_stockevent_update')
} }
} }
}, [socket, initialized]) }, [printServer, initialized])
useEffect(() => { useEffect(() => {
setStockEventsData(stockEvents) setStockEventsData(stockEvents)

View File

@ -1,7 +1,7 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Typography, Tag } from 'antd' // eslint-disable-line import { Typography, Tag } from 'antd' // eslint-disable-line
import React, { useState, useContext, useEffect } from 'react' import React, { useState, useContext, useEffect } from 'react'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import QuestionCircleIcon from '../../Icons/QuestionCircleIcon' import QuestionCircleIcon from '../../Icons/QuestionCircleIcon'
import PauseCircleIcon from '../../Icons/PauseCircleIcon' import PauseCircleIcon from '../../Icons/PauseCircleIcon'
import XMarkCircleIcon from '../../Icons/XMarkCircleIcon' import XMarkCircleIcon from '../../Icons/XMarkCircleIcon'
@ -12,7 +12,7 @@ const SubJobCounter = ({
showIcon = true, showIcon = true,
state = { type: 'complete' } state = { type: 'complete' }
}) => { }) => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
var badgeStatus = 'unknown' var badgeStatus = 'unknown'
var badgeIcon = <QuestionCircleIcon /> var badgeIcon = <QuestionCircleIcon />
@ -21,10 +21,10 @@ const SubJobCounter = ({
const [count, setCount] = useState(0) const [count, setCount] = useState(0)
useEffect(() => { useEffect(() => {
if (socket && !initialized && job?.id) { if (printServer && !initialized && job?.id) {
setInitialized(true) setInitialized(true)
console.log('on notify_subjob_update') console.log('on notify_subjob_update')
socket.on('notify_subjob_update', (statusUpdate) => { printServer.on('notify_subjob_update', (statusUpdate) => {
for (const subJob of job.subJobs) { for (const subJob of job.subJobs) {
if (statusUpdate?._id === subJob.id && statusUpdate?.state) { if (statusUpdate?._id === subJob.id && statusUpdate?.state) {
console.log('statusUpdate', statusUpdate) console.log('statusUpdate', statusUpdate)
@ -34,12 +34,12 @@ const SubJobCounter = ({
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('off notify_subjob_update') console.log('off notify_subjob_update')
socket.off('notify_subjob_update') printServer.off('notify_subjob_update')
} }
} }
}, [socket, initialized, job?.subJobs, job?.id]) }, [printServer, initialized, job?.subJobs, job?.id])
switch (state.type) { switch (state.type) {
case 'draft': case 'draft':

View File

@ -2,7 +2,7 @@ import PropTypes from 'prop-types'
import { Progress, Flex, Button, Space, Tooltip } from 'antd' // eslint-disable-line import { Progress, Flex, Button, Space, Tooltip } from 'antd' // eslint-disable-line
import { CaretLeftOutlined } from '@ant-design/icons' // eslint-disable-line import { CaretLeftOutlined } from '@ant-design/icons' // eslint-disable-line
import React, { useState, useContext, useEffect } from 'react' import React, { useState, useContext, useEffect } from 'react'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import IdText from './IdText' import IdText from './IdText'
import StateTag from './StateTag' import StateTag from './StateTag'
import XMarkIcon from '../../Icons/XMarkIcon' import XMarkIcon from '../../Icons/XMarkIcon'
@ -16,7 +16,7 @@ const SubJobState = ({
showProgress = true, showProgress = true,
showControls = true //eslint-disable-line showControls = true //eslint-disable-line
}) => { }) => {
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [currentState, setCurrentState] = useState( const [currentState, setCurrentState] = useState(
subJob?.state || { subJob?.state || {
type: 'unknown', type: 'unknown',
@ -26,10 +26,10 @@ const SubJobState = ({
const [initialized, setInitialized] = useState(false) const [initialized, setInitialized] = useState(false)
useEffect(() => { useEffect(() => {
if (socket && !initialized && subJob?._id) { if (printServer && !initialized && subJob?._id) {
setInitialized(true) setInitialized(true)
console.log('on notify_subjob_update') console.log('on notify_subjob_update')
socket.on('notify_subjob_update', (statusUpdate) => { printServer.on('notify_subjob_update', (statusUpdate) => {
if (statusUpdate?._id === subJob._id && statusUpdate?.state) { if (statusUpdate?._id === subJob._id && statusUpdate?.state) {
console.log('statusUpdate', statusUpdate) console.log('statusUpdate', statusUpdate)
setCurrentState(statusUpdate.state) setCurrentState(statusUpdate.state)
@ -37,12 +37,12 @@ const SubJobState = ({
}) })
} }
return () => { return () => {
if (socket && initialized) { if (printServer && initialized) {
console.log('off notify_subjob_update') console.log('off notify_subjob_update')
socket.off('notify_subjob_update') printServer.off('notify_subjob_update')
} }
} }
}, [socket, initialized, subJob?._id]) }, [printServer, initialized, subJob?._id])
return ( return (
<Flex gap='small' align={'center'}> <Flex gap='small' align={'center'}>
@ -73,11 +73,11 @@ const SubJobState = ({
<Button <Button
onClick={() => { onClick={() => {
if (currentState.type === 'printing') { if (currentState.type === 'printing') {
socket.emit('printer.print.pause', { printServer.emit('printer.print.pause', {
printerId: subJob.printer printerId: subJob.printer
}) })
} else { } else {
socket.emit('printer.print.resume', { printServer.emit('printer.print.resume', {
printerId: subJob.printer printerId: subJob.printer
}) })
} }
@ -100,7 +100,7 @@ const SubJobState = ({
<Tooltip title='Cancel' arrow={false}> <Tooltip title='Cancel' arrow={false}>
<Button <Button
onClick={() => { onClick={() => {
socket.emit('printer.print.cancel', { printServer.emit('printer.print.cancel', {
printerId: subJob.printer printerId: subJob.printer
}) })
}} }}
@ -117,7 +117,7 @@ const SubJobState = ({
<Tooltip title='Cancel' arrow={false}> <Tooltip title='Cancel' arrow={false}>
<Button <Button
onClick={() => { onClick={() => {
socket.emit('server.job_queue.cancel', { printServer.emit('server.job_queue.cancel', {
subJobId: subJob._id subJobId: subJob._id
}) })
}} }}

View File

@ -6,7 +6,7 @@ import React, { useState, useEffect, useContext, useCallback } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import PrinterState from './PrinterState' import PrinterState from './PrinterState'
import axios from 'axios' import axios from 'axios'
import { SocketContext } from '../context/SocketContext' import { PrintServerContext } from '../context/PrintServerContext'
import PrinterIcon from '../../Icons/PrinterIcon' import PrinterIcon from '../../Icons/PrinterIcon'
import SubJobState from './SubJobState' import SubJobState from './SubJobState'
import SubJobIcon from '../../Icons/SubJobIcon' import SubJobIcon from '../../Icons/SubJobIcon'
@ -18,7 +18,7 @@ const SubJobsTree = ({ jobData, loading }) => {
const [treeData, setTreeData] = useState([]) const [treeData, setTreeData] = useState([])
const [treeLoading, setTreeLoading] = useState(loading) const [treeLoading, setTreeLoading] = useState(loading)
const [error, setError] = useState(null) const [error, setError] = useState(null)
const { socket } = useContext(SocketContext) const { printServer } = useContext(PrintServerContext)
const [messageApi] = message.useMessage() const [messageApi] = message.useMessage()
const [expandedKeys, setExpandedKeys] = useState([]) const [expandedKeys, setExpandedKeys] = useState([])
const [currentJobData, setCurrentJobData] = useState(null) const [currentJobData, setCurrentJobData] = useState(null)
@ -111,9 +111,9 @@ const SubJobsTree = ({ jobData, loading }) => {
initializeData() initializeData()
// Add socket.io event listener for deployment updates // Add printServer.io event listener for deployment updates
if (socket) { if (printServer) {
socket.on('notify_deployment_update', (updateData) => { printServer.on('notify_deployment_update', (updateData) => {
console.log('Received deployment update:', updateData) console.log('Received deployment update:', updateData)
setCurrentJobData((prevData) => { setCurrentJobData((prevData) => {
if (!prevData) return prevData if (!prevData) return prevData
@ -148,7 +148,7 @@ const SubJobsTree = ({ jobData, loading }) => {
return prevData return prevData
}) })
}) })
socket.on('notify_subjob_update', (updateData) => { printServer.on('notify_subjob_update', (updateData) => {
// Handle sub-job updates // Handle sub-job updates
if (updateData.subJobId) { if (updateData.subJobId) {
console.log('Received subjob update:', updateData) console.log('Received subjob update:', updateData)
@ -174,11 +174,11 @@ const SubJobsTree = ({ jobData, loading }) => {
} }
return () => { return () => {
if (socket) { if (printServer) {
socket.off('notify_deployment_update') printServer.off('notify_deployment_update')
} }
} }
}, [jobData, socket]) }, [jobData, printServer])
if (error) { if (error) {
return ( return (

View File

@ -0,0 +1,300 @@
// src/contexts/ApiServerContext.js
import React, {
createContext,
useEffect,
useState,
useContext,
useRef
} from 'react'
import io from 'socket.io-client'
import { message, notification, Modal, Space, Button } from 'antd'
import PropTypes from 'prop-types'
import { AuthContext } from './AuthContext'
import config from '../../../config'
import loglevel from 'loglevel'
import axios from 'axios'
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon'
import ReloadIcon from '../../Icons/ReloadIcon'
const log = loglevel.getLogger('Api Server')
log.setLevel(config.logLevel)
const ApiServerContext = createContext()
const ApiServerProvider = ({ children }) => {
const { token, userProfile } = useContext(AuthContext)
const socketRef = useRef(null)
const [connecting, setConnecting] = useState(false)
const [error, setError] = useState(null)
const [messageApi, contextHolder] = message.useMessage()
const [notificationApi] = notification.useNotification()
const [fetchLoading, setFetchLoading] = useState(false)
const [showErrorModal, setShowErrorModal] = useState(false)
const [errorModalContent, setErrorModalContent] = useState('')
const [retryCallback, setRetryCallback] = useState(null)
useEffect(() => {
if (token) {
log.debug('Token is available, connecting to api server...')
const newSocket = io(config.apiServerUrl, {
reconnectionAttempts: 3,
timeout: 3000,
auth: { token: token }
})
setConnecting(true)
newSocket.on('connect', () => {
log.debug('Api Server connected')
setConnecting(false)
setError(null)
})
newSocket.on('disconnect', () => {
log.debug('Api Server disconnected')
setError('Api Server disconnected')
})
newSocket.on('connect_error', (err) => {
log.error('Api Server connection error:', err)
messageApi.error('Api Server connection error: ' + err.message)
setError('Api Server connection error')
})
newSocket.on('bridge.notification', (data) => {
notificationApi[data.type]({
title: data.title,
message: data.message
})
})
newSocket.on('error', (err) => {
log.error('Api Server error:', err)
setError('Api Server error')
})
socketRef.current = newSocket
// Clean up function
return () => {
if (socketRef.current) {
log.debug('Cleaning up api server connection...')
socketRef.current.disconnect()
socketRef.current = null
}
}
} else if (!token && socketRef.current) {
log.debug('Token not available, disconnecting api server...')
socketRef.current.disconnect()
socketRef.current = null
}
}, [token, messageApi])
const lockObject = (id, type) => {
log.debug('Locking ' + id)
if (socketRef.current && socketRef.current.connected) {
socketRef.current.emit('lock', { _id: id, type: type })
log.debug('Sent lock command for object:', id)
}
}
const unlockObject = (id, type) => {
log.debug('Unlocking ' + id)
if (socketRef.current && socketRef.current.connected == true) {
socketRef.current.emit('unlock', { _id: id, type: type })
log.debug('Sent unlock command for object:', id)
}
}
const fetchObjectLock = async (id, type) => {
if (socketRef.current && socketRef.current.connected == true) {
log.debug('Fetching lock status for ' + id)
return new Promise((resolve) => {
socketRef.current.emit(
'getLock',
{
_id: id,
type: type
},
(lockEvent) => {
log.debug('Received lock event for object:', id, lockEvent)
resolve(lockEvent)
}
)
log.debug('Sent fetch lock command for object:', id)
})
}
}
const onLockEvent = (id, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const eventHandler = (data) => {
if (data._id === id && data?.user !== userProfile._id) {
log.debug(
'Lock update received for object:',
id,
'locked:',
data.locked
)
callback(data)
}
}
socketRef.current.on('notify_lock_update', eventHandler)
log.debug('Registered lock event listener for object:', id)
// Return cleanup function
return () => offLockEvent(id, eventHandler)
}
}
const offLockEvent = (id, eventHandler) => {
if (socketRef.current && socketRef.current.connected == true) {
socketRef.current.off('notify_lock_update', eventHandler)
log.debug('Removed lock event listener for object:', id)
}
}
const onUpdateEvent = (id, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const eventHandler = (data) => {
if (data._id === id && data?.user !== userProfile._id) {
log.debug(
'Update event received for object:',
id,
'updatedAt:',
data.updatedAt
)
callback(data)
}
}
socketRef.current.on('notify_object_update', eventHandler)
log.debug('Registered update event listener for object:', id)
// Return cleanup function
return () => offUpdateEvent(id, eventHandler)
}
}
const offUpdateEvent = (id, eventHandler) => {
if (socketRef.current && socketRef.current.connected == true) {
socketRef.current.off('notify_update', eventHandler)
log.debug('Removed update event listener for object:', id)
}
}
const showError = (content, callback = null) => {
setErrorModalContent(content)
setRetryCallback(() => callback)
setShowErrorModal(true)
}
const handleRetry = () => {
setShowErrorModal(false)
setErrorModalContent('')
if (retryCallback) {
retryCallback()
}
setRetryCallback(null)
}
// Generalized fetchObjectInfo function
const fetchObjectInfo = async (id, type) => {
const fetchUrl = `${config.backendUrl}/${type}s/${id}`
setFetchLoading(true)
log.debug('Fetching from ' + fetchUrl)
try {
const response = await axios.get(fetchUrl, {
headers: {
Accept: 'application/json'
},
withCredentials: true
})
return response.data
} catch (err) {
log.error('Failed to fetch object information:', err)
// Don't automatically show error - let the component handle it
throw err
} finally {
setFetchLoading(false)
}
}
// Update filament information
const updateObjectInfo = async (id, type, value) => {
const updateUrl = `${config.backendUrl}/${type}s/${id}`
log.debug('Updating info for ' + id)
try {
const response = await axios.put(updateUrl, value, {
headers: {
'Content-Type': 'application/json'
},
withCredentials: true
})
log.debug('Filament updated successfully')
if (socketRef.current && socketRef.current.connected == true) {
await socketRef.current.emit('update', {
_id: id,
type: type,
updatedAt: response.data.updatedAt
})
}
return response.data
} catch (err) {
log.error('Failed to update filament information:', err)
// Don't automatically show error - let the component handle it
throw err
}
}
return (
<ApiServerContext.Provider
value={{
apiServer: socketRef.current,
error,
connecting,
lockObject,
unlockObject,
fetchObjectLock,
updateObjectInfo,
onLockEvent,
onUpdateEvent,
offUpdateEvent,
fetchObjectInfo,
fetchLoading,
showError
}}
>
{contextHolder}
{children}
<Modal
title={
<Space size={'middle'}>
<ExclamationOctagonIcon />
Error
</Space>
}
open={showErrorModal}
okText='OK'
style={{ maxWidth: 430 }}
closable={false}
centered
maskClosable={true}
footer={[
<Button key='retry' icon={<ReloadIcon />} onClick={handleRetry}>
Retry
</Button>
]}
>
{errorModalContent}
</Modal>
</ApiServerContext.Provider>
)
}
ApiServerProvider.propTypes = {
children: PropTypes.node.isRequired
}
export { ApiServerContext, ApiServerProvider }

View File

@ -1,212 +0,0 @@
// src/contexts/NotificationContext.js
import React, {
createContext,
useEffect,
useState,
useContext,
useRef
} from 'react'
import io from 'socket.io-client'
import { message, notification, Drawer } from 'antd'
import PropTypes from 'prop-types'
import { AuthContext } from './AuthContext'
import config from '../../../config'
import NotificationCenter from '../common/NotificationCenter'
const NotificationContext = createContext()
const NotificationProvider = ({ children }) => {
const { token } = useContext(AuthContext)
const socketRef = useRef(null)
const [connecting, setConnecting] = useState(false)
const [error, setError] = useState(null)
const [notificationVisible, setNotificationVisible] = useState(false)
const [notifications, setNotifications] = useState([])
const [messageApi, contextHolder] = message.useMessage()
const [notificationApi] = notification.useNotification()
useEffect(() => {
if (token) {
console.log(
'Token is available, connecting to notification web socket server...'
)
const newSocket = io(
config.notificationWsUrl || `${config.wsUrl}/notifications`,
{
reconnectionAttempts: 3,
timeout: 3000,
auth: { token: token }
}
)
setConnecting(true)
newSocket.on('connect', () => {
console.log('Notification socket connected')
setConnecting(false)
setError(null)
})
newSocket.on('disconnect', () => {
console.log('Notification socket disconnected')
setError('Notification socket disconnected')
})
newSocket.on('connect_error', (err) => {
console.error('Notification socket connection error:', err)
messageApi.error('Notification socket connection error: ' + err.message)
setError('Notification socket connection error')
})
newSocket.on('notification.new', (data) => {
console.log('New notification received:', data)
// Add new notification to state
setNotifications((prev) => [data, ...prev])
// Show toast notification
notificationApi.info({
message: data.title || 'New Notification',
description: data.message,
placement: 'topRight',
duration: 4.5
})
})
newSocket.on('notification.update', (data) => {
console.log('Notification updated:', data)
setNotifications((prev) =>
prev.map((notification) => {
if (notification._id === data._id) {
return { ...notification, ...data }
}
return notification
})
)
})
newSocket.on('notification.delete', (data) => {
console.log('Notification deleted:', data)
setNotifications((prev) =>
prev.filter((notification) => notification._id !== data._id)
)
})
newSocket.on('error', (err) => {
console.error('Notification socket error:', err)
setError('Notification socket error')
})
socketRef.current = newSocket
// Clean up function
return () => {
if (socketRef.current) {
console.log('Cleaning up notification socket connection...')
socketRef.current.disconnect()
socketRef.current = null
}
}
} else if (!token && socketRef.current) {
console.log('Token not available, disconnecting notification socket...')
socketRef.current.disconnect()
socketRef.current = null
}
}, [token, messageApi, notificationApi])
const showNotificationCenter = () => {
setNotificationVisible(true)
}
const hideNotificationCenter = () => {
setNotificationVisible(false)
}
const toggleNotificationCenter = () => {
setNotificationVisible((prev) => !prev)
}
const updateNotifications = (newNotifications) => {
setNotifications(newNotifications)
}
const addNotification = (notification) => {
setNotifications((prev) => [notification, ...prev])
}
const removeNotification = (notificationId) => {
setNotifications((prev) =>
prev.filter((notification) => notification._id !== notificationId)
)
}
const markNotificationAsRead = (notificationId) => {
setNotifications((prev) =>
prev.map((notification) => {
if (notification._id === notificationId) {
return { ...notification, read: true }
}
return notification
})
)
}
const markAllNotificationsAsRead = () => {
setNotifications((prev) =>
prev.map((notification) => ({ ...notification, read: true }))
)
}
const clearAllNotifications = () => {
setNotifications([])
}
const getUnreadCount = () => {
return notifications.filter((notification) => !notification.read).length
}
return (
<NotificationContext.Provider
value={{
socket: socketRef.current,
error,
connecting,
notifications,
notificationVisible,
unreadCount: getUnreadCount(),
showNotificationCenter,
hideNotificationCenter,
toggleNotificationCenter,
updateNotifications,
addNotification,
removeNotification,
markNotificationAsRead,
markAllNotificationsAsRead,
clearAllNotifications
}}
>
{contextHolder}
{children}
{/* Notification Drawer */}
<Drawer
title='Notifications'
placement='right'
width={400}
onClose={hideNotificationCenter}
open={notificationVisible}
style={{
zIndex: 1001
}}
>
<NotificationCenter visible={notificationVisible} />
</Drawer>
</NotificationContext.Provider>
)
}
NotificationProvider.propTypes = {
children: PropTypes.node.isRequired
}
export { NotificationContext, NotificationProvider }

View File

@ -1,4 +1,4 @@
// src/contexts/SocketContext.js // src/contexts/PrintServerContext.js
import React, { import React, {
createContext, createContext,
useEffect, useEffect,
@ -11,10 +11,13 @@ import { message, notification } from 'antd'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { AuthContext } from './AuthContext' import { AuthContext } from './AuthContext'
import config from '../../../config' import config from '../../../config'
import loglevel from 'loglevel'
const log = loglevel.getLogger('Print Server')
log.setLevel(config.logLevel)
const SocketContext = createContext() const PrintServerContext = createContext()
const SocketProvider = ({ children }) => { const PrintServerProvider = ({ children }) => {
const { token } = useContext(AuthContext) const { token } = useContext(AuthContext)
const socketRef = useRef(null) const socketRef = useRef(null)
const [connecting, setConnecting] = useState(false) const [connecting, setConnecting] = useState(false)
@ -24,9 +27,9 @@ const SocketProvider = ({ children }) => {
useEffect(() => { useEffect(() => {
if (token) { if (token) {
console.log('Token is available, connecting to web socket server...') log.debug('Token is available, connecting to print server...')
const newSocket = io(config.wsUrl, { const newSocket = io(config.printServerUrl, {
reconnectionAttempts: 3, reconnectionAttempts: 3,
timeout: 3000, timeout: 3000,
auth: { token: token } auth: { token: token }
@ -35,20 +38,20 @@ const SocketProvider = ({ children }) => {
setConnecting(true) setConnecting(true)
newSocket.on('connect', () => { newSocket.on('connect', () => {
console.log('Socket connected') log.debug('Print server connected')
setConnecting(false) setConnecting(false)
setError(null) setError(null)
}) })
newSocket.on('disconnect', () => { newSocket.on('disconnect', () => {
console.log('Socket disconnected') log.debug('Print server disconnected')
setError('Socket disconnected') setError('Print server disconnected')
}) })
newSocket.on('connect_error', (err) => { newSocket.on('connect_error', (err) => {
console.error('Socket connection error:', err) log.error('Print server connection error:', err)
messageApi.error('Socket connection error: ' + err.message) messageApi.error('Print server connection error: ' + err.message)
setError('Socket connection error') setError('Print server connection error')
}) })
newSocket.on('bridge.notification', (data) => { newSocket.on('bridge.notification', (data) => {
@ -59,8 +62,8 @@ const SocketProvider = ({ children }) => {
}) })
newSocket.on('error', (err) => { newSocket.on('error', (err) => {
console.error('Socket error:', err) log.error('Print server error:', err)
setError('Socket error') setError('Print server error')
}) })
socketRef.current = newSocket socketRef.current = newSocket
@ -68,30 +71,30 @@ const SocketProvider = ({ children }) => {
// Clean up function // Clean up function
return () => { return () => {
if (socketRef.current) { if (socketRef.current) {
console.log('Cleaning up socket connection...') log.debug('Cleaning up socket connection...')
socketRef.current.disconnect() socketRef.current.disconnect()
socketRef.current = null socketRef.current = null
} }
} }
} else if (!token && socketRef.current) { } else if (!token && socketRef.current) {
console.log('Token not available, disconnecting socket...') log.debug('Token not available, disconnecting socket...')
socketRef.current.disconnect() socketRef.current.disconnect()
socketRef.current = null socketRef.current = null
} }
}, [token, messageApi]) }, [token, messageApi])
return ( return (
<SocketContext.Provider <PrintServerContext.Provider
value={{ socket: socketRef.current, error, connecting }} value={{ printServer: socketRef.current, error, connecting }}
> >
{contextHolder} {contextHolder}
{children} {children}
</SocketContext.Provider> </PrintServerContext.Provider>
) )
} }
SocketProvider.propTypes = { PrintServerProvider.propTypes = {
children: PropTypes.node.isRequired children: PropTypes.node.isRequired
} }
export { SocketContext, SocketProvider } export { PrintServerContext, PrintServerProvider }

View File

@ -345,24 +345,6 @@ const SpotlightProvider = ({ children }) => {
} }
} }
// Add keyboard shortcut listener
useEffect(() => {
const handleKeyPress = (e) => {
if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'p') {
e.preventDefault() // Prevent browser's default behavior
showSpotlight()
}
}
// Add event listener
window.addEventListener('keydown', handleKeyPress)
// Clean up
return () => {
window.removeEventListener('keydown', handleKeyPress)
}
}, [])
// Focus and select text in input when modal becomes visible // Focus and select text in input when modal becomes visible
useEffect(() => { useEffect(() => {
if (showModal && inputRef.current) { if (showModal && inputRef.current) {

View File

@ -0,0 +1,7 @@
import React from 'react'
import Icon from '@ant-design/icons'
import { ReactComponent as CustomIconSvg } from '../../assets/icons/lockicon.min.svg'
const LockIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default LockIcon

View File

@ -1,11 +1,15 @@
const config = { const config = {
development: { development: {
backendUrl: 'http://192.168.68.53:8080', backendUrl: 'http://192.168.68.53:8080',
wsUrl: 'ws://192.168.68.53:8081' printServerUrl: 'ws://192.168.68.53:8081',
apiServerUrl: 'ws://192.168.68.53:9090',
logLevel: 'trace'
}, },
production: { production: {
backendUrl: 'http://192.168.68.53:8080', // Replace with your production backend URL backendUrl: 'http://192.168.68.53:8080',
wsUrl: 'http://192.168.68.53:8081' // Replace with your production WebSocket URL printServerUrl: 'ws://192.168.68.53:8081',
apiServerUrl: 'ws://192.168.68.53:9090',
logLevel: 'error'
} }
} }