diff --git a/package-lock.json b/package-lock.json
index 4bfaf39..005bd84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26,7 +26,7 @@
"eslint-plugin-react-hooks": "^5.2.0",
"gcode-preview": "^2.18.0",
"keycloak-js": "^26.2.0",
- "log4js": "^6.9.1",
+ "loglevel": "^1.9.2",
"moment": "*",
"prettier": "^3.5.3",
"prettier-eslint": "^16.4.2",
@@ -9986,15 +9986,6 @@
"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": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
@@ -16008,27 +15999,10 @@
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
"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": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
@@ -21486,12 +21460,6 @@
"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": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -22819,52 +22787,6 @@
"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": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
diff --git a/package.json b/package.json
index e1b964e..f7b0ca1 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
"eslint-plugin-react-hooks": "^5.2.0",
"gcode-preview": "^2.18.0",
"keycloak-js": "^26.2.0",
- "log4js": "^6.9.1",
+ "loglevel": "^1.9.2",
"moment": "*",
"prettier": "^3.5.3",
"prettier-eslint": "^16.4.2",
diff --git a/src/App.css b/src/App.css
index 709352a..8c52b6d 100644
--- a/src/App.css
+++ b/src/App.css
@@ -22,11 +22,14 @@
.ant-input,
.ant-input-number .ant-input-number-input,
.ant-segmented-item-label,
-.ant-badge-status-text {
+.ant-badge-status-text,
+.ant-tree-title,
+.ant-select {
font-family: 'DM Sans';
}
-.ant-typography code {
+.ant-typography code,
+.ant-typography pre {
font-family: 'DM Mono';
}
@@ -115,6 +118,10 @@ code {
padding: 0 !important;
}
+.ant-popover-inner:has(.keyboard-shortcut-tooltip) {
+ padding: 8px !important;
+}
+
/* --- Start of src/index.css --- */
body {
margin: 0;
diff --git a/src/App.jsx b/src/App.jsx
index 43b8132..d6e9d07 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -44,7 +44,7 @@ import StockAuditInfo from './components/Dashboard/Inventory/StockAudits/StockAu
import Dashboard from './components/Dashboard/Dashboard.jsx'
import PrivateRoute from './components/PrivateRoute'
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 { SpotlightProvider } from './components/Dashboard/context/SpotlightContext.js'
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 SessionStorage from './components/Dashboard/Developer/SessionStorage.jsx'
import AuthContextDebug from './components/Dashboard/Developer/AuthContextDebug.jsx'
-import SocketContextDebug from './components/Dashboard/Developer/SocketContextDebug.jsx'
-import { NotificationProvider } from './components/Dashboard/context/NotificationContext.js'
+import PrintServerContextDebug from './components/Dashboard/Developer/PrintServerContextDebug.jsx'
+import { ApiServerProvider } from './components/Dashboard/context/ApiServerContext.js'
import Users from './components/Dashboard/Management/Users.jsx'
import UserInfo from './components/Dashboard/Management/Users/UserInfo.jsx'
@@ -74,8 +74,8 @@ const AppContent = () => {
-
-
+
+
{
element={}
/>
}
+ path='developer/printservercontextdebug'
+ element={}
/>
{
/>
-
-
+
+
diff --git a/src/assets/icons/lockicon.afdesign b/src/assets/icons/lockicon.afdesign
new file mode 100644
index 0000000..5ed851b
Binary files /dev/null and b/src/assets/icons/lockicon.afdesign differ
diff --git a/src/assets/icons/lockicon.min.svg b/src/assets/icons/lockicon.min.svg
new file mode 100644
index 0000000..f07fb04
--- /dev/null
+++ b/src/assets/icons/lockicon.min.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/lockicon.svg b/src/assets/icons/lockicon.svg
new file mode 100644
index 0000000..cd9f0bc
--- /dev/null
+++ b/src/assets/icons/lockicon.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/components/Dashboard/Developer/DeveloperSidebar.jsx b/src/components/Dashboard/Developer/DeveloperSidebar.jsx
index 1e359f8..c6c68a5 100644
--- a/src/components/Dashboard/Developer/DeveloperSidebar.jsx
+++ b/src/components/Dashboard/Developer/DeveloperSidebar.jsx
@@ -1,29 +1,35 @@
import React from 'react'
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
+import { Typography } from 'antd'
+
+const { Text } = Typography
const items = [
{
key: 'sessionstorage',
label: 'Session Storage',
+ icon: 🗃️,
path: '/dashboard/developer/sessionstorage'
},
{
key: 'authcontextdebug',
- label: 'Auth Context Debug',
+ label: 'Auth Debug',
+ icon: 🔐,
path: '/dashboard/developer/authcontextdebug'
},
{
- key: 'socketcontextdebug',
- label: 'Socket Context Debug',
- path: '/dashboard/developer/socketcontextdebug'
+ key: 'printservercontextdebug',
+ label: 'Print Server Debug',
+ icon: 🖨️,
+ path: '/dashboard/developer/printservercontextdebug'
}
]
const routeKeyMap = {
'/dashboard/developer/sessionstorage': 'sessionstorage',
'/dashboard/developer/authcontext': 'authcontextdebug',
- '/dashboard/developer/socketcontext': 'socketcontextdebug'
+ '/dashboard/developer/printservercontext': 'printservercontextdebug'
}
const DeveloperSidebar = (props) => {
diff --git a/src/components/Dashboard/Developer/SocketContextDebug.jsx b/src/components/Dashboard/Developer/PrintServerContextDebug.jsx
similarity index 82%
rename from src/components/Dashboard/Developer/SocketContextDebug.jsx
rename to src/components/Dashboard/Developer/PrintServerContextDebug.jsx
index 54b0b53..e5f3b9b 100644
--- a/src/components/Dashboard/Developer/SocketContextDebug.jsx
+++ b/src/components/Dashboard/Developer/PrintServerContextDebug.jsx
@@ -9,13 +9,13 @@ import {
message
} from 'antd'
import ReloadIcon from '../../Icons/ReloadIcon.jsx'
-import { SocketContext } from '../context/SocketContext.js'
+import { PrintServerContext } from '../context/PrintServerContext.js'
import BoolDisplay from '../common/BoolDisplay.jsx'
const { Text, Paragraph } = Typography
-const SocketContextDebug = () => {
- const { socket, error, connecting } = useContext(SocketContext)
+const PrintServerContextDebug = () => {
+ const { printServer, error, connecting } = useContext(PrintServerContext)
const [msgApi, contextHolder] = message.useMessage()
const actionItems = {
@@ -36,9 +36,9 @@ const SocketContextDebug = () => {
// Helper to display socket info safely
const getSocketInfo = () => {
- if (!socket) return 'n/a'
+ if (!printServer) return 'n/a'
// 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)
}
@@ -55,7 +55,7 @@ const SocketContextDebug = () => {
-
+
@@ -74,4 +74,4 @@ const SocketContextDebug = () => {
)
}
-export default SocketContextDebug
+export default PrintServerContextDebug
diff --git a/src/components/Dashboard/Inventory/FilamentStocks.jsx b/src/components/Dashboard/Inventory/FilamentStocks.jsx
index 5be217f..c9275a6 100644
--- a/src/components/Dashboard/Inventory/FilamentStocks.jsx
+++ b/src/components/Dashboard/Inventory/FilamentStocks.jsx
@@ -16,7 +16,7 @@ import {
} from 'antd'
import { AuthContext } from '../context/AuthContext'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import NewFilamentStock from './FilamentStocks/NewFilamentStock'
import IdText from '../common/IdText'
@@ -41,7 +41,7 @@ const { Text } = Typography
const FilamentStocks = () => {
const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate()
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false)
const tableRef = useRef()
@@ -213,9 +213,9 @@ const FilamentStocks = () => {
)
React.useEffect(() => {
- if (socket && !initialized) {
+ if (printServer && !initialized) {
setInitialized(true)
- socket.on('notify_filamentstock_update', (updateData) => {
+ printServer.on('notify_filamentstock_update', (updateData) => {
console.log('Received filament stock update:', updateData)
if (tableRef.current) {
tableRef.current.updateData(updateData._id, updateData)
@@ -224,12 +224,12 @@ const FilamentStocks = () => {
}
return () => {
- if (socket && initialized) {
+ if (printServer && initialized) {
console.log('Deregistering filament stock update listener')
- socket.off('notify_filamentstock_update')
+ printServer.off('notify_filamentstock_update')
}
}
- }, [socket, initialized])
+ }, [printServer, initialized])
const getFilamentStockActionItems = (id) => {
return {
diff --git a/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx b/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx
index 1e3a310..2f59597 100644
--- a/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx
+++ b/src/components/Dashboard/Inventory/FilamentStocks/FilamentStockInfo.jsx
@@ -19,7 +19,7 @@ import {
} from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
import IdText from '../../common/IdText'
-import { SocketContext } from '../../context/SocketContext'
+import { PrintServerContext } from '../../context/PrintServerContext'
import FilamentStockState from '../../common/FilamentStockState'
import StockEventTable from '../../common/StockEventTable'
import useCollapseState from '../../hooks/useCollapseState'
@@ -48,7 +48,7 @@ const FilamentStockInfo = () => {
'filamentStockId'
)
const [form] = Form.useForm()
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [collapseState, updateCollapseState] = useCollapseState(
'FilamentStockInfo',
{
@@ -75,9 +75,9 @@ const FilamentStockInfo = () => {
// Add WebSocket event listener for real-time updates
useEffect(() => {
- if (socket && !initialized && filamentStockId) {
+ if (printServer && !initialized && filamentStockId) {
setInitialized(true)
- socket.on('notify_filamentstock_update', (statusUpdate) => {
+ printServer.on('notify_filamentstock_update', (statusUpdate) => {
console.log('GOT FILAMENT STOCK UPDATE', statusUpdate)
setFilamentStockData((prevData) => {
if (statusUpdate?._id === filamentStockId) {
@@ -91,12 +91,12 @@ const FilamentStockInfo = () => {
})
}
return () => {
- if (socket && initialized) {
+ if (printServer && initialized) {
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 () => {
try {
diff --git a/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx b/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx
index cb3598d..2a7b7d8 100644
--- a/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx
+++ b/src/components/Dashboard/Inventory/FilamentStocks/LoadFilamentStock.jsx
@@ -11,7 +11,7 @@ import {
} from 'antd'
import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types'
-import { SocketContext } from '../../context/SocketContext'
+import { PrintServerContext } from '../../context/PrintServerContext'
import FilamentStockSelect from '../../common/FilamentStockSelect'
import PrinterSelect from '../../common/PrinterSelect'
@@ -38,7 +38,7 @@ const LoadFilamentStock = ({
filamentStockLoaded: PropTypes.bool
}
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const initialLoadFilamentStockForm = {
printer: printer,
@@ -93,16 +93,16 @@ const LoadFilamentStock = ({
console.log(statusUpdate)
}
- socket.emit('printer.objects.subscribe', params)
- socket.emit('printer.objects.query', params)
- socket.on('notify_status_update', notifyStatusUpdate)
+ printServer.emit('printer.objects.subscribe', params)
+ printServer.emit('printer.objects.query', params)
+ printServer.on('notify_status_update', notifyStatusUpdate)
return () => {
- socket.off('notify_status_update', notifyStatusUpdate)
- socket.emit('printer.objects.unsubscribe', params)
+ printServer.off('notify_status_update', notifyStatusUpdate)
+ printServer.emit('printer.objects.unsubscribe', params)
}
}
- }, [socket, loadFilamentStockFormValues.printer])
+ }, [printServer, loadFilamentStockFormValues.printer])
React.useEffect(() => {
loadFilamentStockForm
@@ -170,7 +170,7 @@ const LoadFilamentStock = ({
try {
// Set the extruder temperature
- await socket.emit('printer.filamentstock.load', {
+ await printServer.emit('printer.filamentstock.load', {
printerId: loadFilamentStockFormValues.printer._id,
filamentStockId: loadFilamentStockFormValues.filamentStock._id
})
diff --git a/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx b/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx
index 215880f..dd1f3df 100644
--- a/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx
+++ b/src/components/Dashboard/Inventory/FilamentStocks/UnloadFilamentStock.jsx
@@ -2,7 +2,7 @@ import React, { useState, useContext, useEffect } from 'react'
import { Form, Button, Typography, Flex, Steps, Divider, Alert } from 'antd'
import { useMediaQuery } from 'react-responsive'
import PropTypes from 'prop-types'
-import { SocketContext } from '../../context/SocketContext'
+import { PrintServerContext } from '../../context/PrintServerContext'
import PrinterSelect from '../../common/PrinterSelect'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
@@ -18,7 +18,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
printer: PropTypes.object
}
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const isMobile = useMediaQuery({ maxWidth: 768 })
const initialUnloadFilamentStockForm = {
@@ -66,16 +66,16 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
}
}
- socket.emit('printer.objects.subscribe', params)
- socket.emit('printer.objects.query', params)
- socket.on('notify_status_update', notifyStatusUpdate)
+ printServer.emit('printer.objects.subscribe', params)
+ printServer.emit('printer.objects.query', params)
+ printServer.on('notify_status_update', notifyStatusUpdate)
return () => {
- socket.off('notify_status_update', notifyStatusUpdate)
- socket.emit('printer.objects.unsubscribe', params)
+ printServer.off('notify_status_update', notifyStatusUpdate)
+ printServer.emit('printer.objects.unsubscribe', params)
}
}
- }, [socket, unloadFilamentStockFormValues.printer])
+ }, [printServer, unloadFilamentStockFormValues.printer])
React.useEffect(() => {
if (reset) {
@@ -109,7 +109,7 @@ const UnloadFilamentStock = ({ onOk, reset, printer = null }) => {
const handleUnloadFilamentStock = async () => {
setUnloadFilamentStockLoading(true)
// Send G-code to retract the filament
- await socket.emit('printer.gcode.script', {
+ await printServer.emit('printer.gcode.script', {
printerId: unloadFilamentStockFormValues.printer._id,
script: `_CLIENT_LINEAR_MOVE E=-200 F=1000`
})
diff --git a/src/components/Dashboard/Inventory/StockAudits.jsx b/src/components/Dashboard/Inventory/StockAudits.jsx
index 2febd96..28f0272 100644
--- a/src/components/Dashboard/Inventory/StockAudits.jsx
+++ b/src/components/Dashboard/Inventory/StockAudits.jsx
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'
import { Button, Flex, Space, message, Dropdown, Typography } from 'antd'
import { AuthContext } from '../context/AuthContext'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import IdText from '../common/IdText'
import StockAuditIcon from '../../Icons/StockAuditIcon'
@@ -20,16 +20,16 @@ const { Text } = Typography
const StockAudits = () => {
const [messageApi, contextHolder] = message.useMessage()
const navigate = useNavigate()
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false)
const tableRef = useRef()
const { authenticated } = useContext(AuthContext)
React.useEffect(() => {
- if (socket && !initialized) {
+ if (printServer && !initialized) {
setInitialized(true)
- socket.on('notify_stockaudit_update', (updateData) => {
+ printServer.on('notify_stockaudit_update', (updateData) => {
console.log('Received stock audit update:', updateData)
if (tableRef.current) {
tableRef.current.updateData(updateData._id, updateData)
@@ -38,12 +38,12 @@ const StockAudits = () => {
}
return () => {
- if (socket && initialized) {
+ if (printServer && initialized) {
console.log('Deregistering stock audit update listener')
- socket.off('notify_stockaudit_update')
+ printServer.off('notify_stockaudit_update')
}
}
- }, [socket, initialized])
+ }, [printServer, initialized])
const getStockAuditActionItems = (id) => {
return {
diff --git a/src/components/Dashboard/Inventory/StockEvents.jsx b/src/components/Dashboard/Inventory/StockEvents.jsx
index 3431206..ce14d3a 100644
--- a/src/components/Dashboard/Inventory/StockEvents.jsx
+++ b/src/components/Dashboard/Inventory/StockEvents.jsx
@@ -11,7 +11,7 @@ import {
} from 'antd'
import { AuthContext } from '../context/AuthContext'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import IdText from '../common/IdText'
import TimeDisplay from '../common/TimeDisplay'
import ReloadIcon from '../../Icons/ReloadIcon'
@@ -31,7 +31,7 @@ import StockEventIcon from '../../Icons/StockEventIcon'
const { Text } = Typography
const StockEvents = () => {
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [initialized, setInitialized] = useState(false)
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('StockEvents')
@@ -225,9 +225,9 @@ const StockEvents = () => {
React.useEffect(() => {
// Add WebSocket event listener for real-time updates
- if (socket && !initialized) {
+ if (printServer && !initialized) {
setInitialized(true)
- socket.on('notify_stockevent_update', (updateData) => {
+ printServer.on('notify_stockevent_update', (updateData) => {
console.log('Received stock event update:', updateData)
if (tableRef.current) {
tableRef.current.updateData(updateData._id, updateData)
@@ -236,12 +236,12 @@ const StockEvents = () => {
}
return () => {
- if (socket && initialized) {
+ if (printServer && initialized) {
console.log('Deregistering stock event update listener')
- socket.off('notify_stockevent_update')
+ printServer.off('notify_stockevent_update')
}
}
- }, [socket, initialized])
+ }, [printServer, initialized])
const actionItems = {
items: [
diff --git a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx
index d52089b..adf0696 100644
--- a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx
+++ b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx
@@ -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 axios from 'axios'
import {
Descriptions,
Spin,
@@ -15,13 +14,15 @@ import {
InputNumber,
ColorPicker,
Select,
- Collapse,
Dropdown,
Popover,
Checkbox,
- Card
+ Card,
+ Tag
} 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 ReloadIcon from '../../../Icons/ReloadIcon'
import EditIcon from '../../../Icons/EditIcon.jsx'
@@ -32,19 +33,25 @@ import VendorSelect from '../../common/VendorSelect'
import useCollapseState from '../../hooks/useCollapseState'
import AuditLogTable from '../../common/AuditLogTable'
import DashboardNotes from '../../common/DashboardNotes'
+import InfoCollapse from '../../common/InfoCollapse'
-import config from '../../../../config.js'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.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 [filamentData, setFilamentData] = useState(null)
const [fetchLoading, setFetchLoading] = useState(true)
const [editLoading, setEditLoading] = useState(false)
- const [error, setError] = useState(null)
+ const [lockUser, setLockUser] = useState(null)
+ const [initialized, setInitialized] = useState(false)
const location = useLocation()
const [messageApi, contextHolder] = message.useMessage()
const filamentId = new URLSearchParams(location.search).get('filamentId')
@@ -59,12 +66,36 @@ const FilamentInfo = () => {
auditLogs: true
}
)
+ const {
+ apiServer,
+ fetchObjectInfo,
+ updateObjectInfo,
+ lockObject,
+ unlockObject,
+ onLockEvent,
+ onUpdateEvent,
+ fetchObjectLock,
+ showError
+ } = useContext(ApiServerContext)
- useEffect(() => {
- if (filamentId) {
- fetchFilamentDetails()
+ // Define the event handler function
+ const lockEventHandler = useCallback((lockEvent) => {
+ 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(() => {
if (filamentData) {
@@ -83,32 +114,84 @@ const FilamentInfo = () => {
}
}, [filamentData, form])
- const fetchFilamentDetails = async () => {
+ const fetchFilamentInfo = useCallback(async () => {
try {
setFetchLoading(true)
- const response = await axios.get(
- `${config.backendUrl}/filaments/${filamentId}`,
- {
- headers: {
- Accept: 'application/json'
- },
- 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 {
+ const data = await fetchObjectInfo(filamentId, 'filament')
+ const lockEvent = await fetchObjectLock(filamentId, 'filament')
+ setLockUser(lockEvent?.user || null)
+ setFilamentData(data)
+ form.setFieldsValue(data)
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 = () => {
updateCollapseState('info', true)
setIsEditing(true)
+ lockObject(filamentId, 'filament')
}
const cancelEditing = () => {
@@ -128,34 +211,15 @@ const FilamentInfo = () => {
})
}
setIsEditing(false)
+ unlockObject(filamentId, 'filament')
}
- const updateFilamentInfo = async () => {
+ const handleUpdateFilamentInfo = async () => {
try {
const values = await form.validateFields()
setEditLoading(true)
- await axios.put(
- `${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
- }
- )
+ await updateFilamentInfo()
// Update the local state with the new values
setFilamentData({ ...filamentData, ...values })
@@ -168,8 +232,13 @@ const FilamentInfo = () => {
}
console.error('Failed to update filament information:', err)
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 {
- fetchFilamentDetails()
+ fetchFilamentInfo()
setEditLoading(false)
}
}
@@ -184,7 +253,7 @@ const FilamentInfo = () => {
],
onClick: ({ key }) => {
if (key === 'reload') {
- fetchFilamentDetails()
+ fetchFilamentInfo()
}
}
}
@@ -215,20 +284,6 @@ const FilamentInfo = () => {
)
}
- if (error) {
- return (
-
- {error || 'Filament not found'}
- } onClick={fetchFilamentDetails}>
- Retry
-
-
- )
- }
-
return (
<>
{contextHolder}
@@ -238,17 +293,34 @@ const FilamentInfo = () => {
style={{ height: '100%', minHeight: 0 }}
>
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {lockUser && (
+
+ }
+ style={{ margin: 0 }}
+ color={'orange'}
+ />
+
+
+ )}
{isEditing ? (
@@ -256,7 +328,7 @@ const FilamentInfo = () => {
}
type='primary'
- onClick={updateFilamentInfo}
+ onClick={handleUpdateFilamentInfo}
loading={editLoading}
disabled={editLoading}
/>
@@ -267,387 +339,325 @@ const FilamentInfo = () => {
/>
>
) : (
- } onClick={startEditing} />
+ }
+ onClick={startEditing}
+ disabled={lockUser !== null || fetchLoading}
+ />
)}
- {error ? (
-
- {error || 'Print job not found'}
- } onClick={fetchFilamentDetails}>
- Retry
-
-
- ) : (
-
-
-
- updateCollapseState('info', keys.length > 0)
- }
- expandIcon={({ isActive }) => (
-
- )}
- className='no-h-padding-collapse no-t-padding-collapse'
+
- )}
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) =>
+ updateCollapseState('auditLogs', expanded)
+ }
+ key='auditLogs'
+ >
+
+
+
+
>
)
diff --git a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx
index 3ef9fc3..5474888 100644
--- a/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx
+++ b/src/components/Dashboard/Production/GCodeFiles/NewGCodeFile.jsx
@@ -72,7 +72,7 @@ const NewGCodeFile = ({ onOk, reset }) => {
const { token, authenticated } = useContext(AuthContext)
// eslint-disable-next-line
- const fetchFilamentDetails = async () => {
+ const fetchFilamentInfo = async () => {
if (!authenticated) {
return
}
diff --git a/src/components/Dashboard/Production/Jobs.jsx b/src/components/Dashboard/Production/Jobs.jsx
index be54fc8..e8ca155 100644
--- a/src/components/Dashboard/Production/Jobs.jsx
+++ b/src/components/Dashboard/Production/Jobs.jsx
@@ -17,7 +17,7 @@ import {
} from 'antd'
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 JobState from '../common/JobState.jsx'
import SubJobCounter from '../common/SubJobCounter.jsx'
@@ -257,7 +257,7 @@ const Jobs = () => {
]
const { authenticated } = useContext(AuthContext)
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [columnVisibility, updateColumnVisibility] = useColumnVisibility(
'Jobs',
@@ -265,9 +265,9 @@ const Jobs = () => {
)
const handleDeployJob = (jobId) => {
- if (socket) {
+ if (printServer) {
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) {
notificationApi.error({
message: 'Print job deployment failed',
diff --git a/src/components/Dashboard/Production/Jobs/JobInfo.jsx b/src/components/Dashboard/Production/Jobs/JobInfo.jsx
index 36e2925..0aed559 100644
--- a/src/components/Dashboard/Production/Jobs/JobInfo.jsx
+++ b/src/components/Dashboard/Production/Jobs/JobInfo.jsx
@@ -21,7 +21,7 @@ import TimeDisplay from '../../common/TimeDisplay'
import JobState from '../../common/JobState'
import IdText from '../../common/IdText'
import SubJobsTree from '../../common/SubJobsTree'
-import { SocketContext } from '../../context/SocketContext'
+import { PrintServerContext } from '../../context/PrintServerContext'
import GCodeFileIcon from '../../../Icons/GCodeFileIcon'
import ReloadIcon from '../../../Icons/ReloadIcon'
import useCollapseState from '../../hooks/useCollapseState'
@@ -42,7 +42,7 @@ const JobInfo = () => {
const location = useLocation()
const [messageApi] = message.useMessage()
const jobId = new URLSearchParams(location.search).get('jobId')
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [collapseState, updateCollapseState] = useCollapseState('JobInfo', {
info: true,
subJobs: true,
@@ -57,8 +57,8 @@ const JobInfo = () => {
}, [jobId])
useEffect(() => {
- if (socket && jobId) {
- socket.on('notify_job_update', (updateData) => {
+ if (printServer && jobId) {
+ printServer.on('notify_job_update', (updateData) => {
if (updateData._id === jobId) {
setJobData((prevData) => {
if (!prevData) return prevData
@@ -73,11 +73,11 @@ const JobInfo = () => {
}
return () => {
- if (socket) {
- socket.off('notify_job_update')
+ if (printServer) {
+ printServer.off('notify_job_update')
}
}
- }, [socket, jobId])
+ }, [printServer, jobId])
const fetchJobDetails = async () => {
try {
diff --git a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx
index 3ba361f..8f8c689 100644
--- a/src/components/Dashboard/Production/Printers/ControlPrinter.jsx
+++ b/src/components/Dashboard/Production/Printers/ControlPrinter.jsx
@@ -22,7 +22,7 @@ import {
} from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
-import { SocketContext } from '../../context/SocketContext'
+import { PrintServerContext } from '../../context/PrintServerContext'
import PrinterTemperaturePanel from '../../common/PrinterTemperaturePanel'
import PrinterPositionPanel from '../../common/PrinterPositionPanel'
@@ -106,7 +106,7 @@ const ControlPrinter = () => {
)
}, [componentVisibility])
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const { authenticated } = useContext(AuthContext)
// Fetch printer details when the component mounts
@@ -143,9 +143,9 @@ const ControlPrinter = () => {
// Add WebSocket event listener for real-time updates
useEffect(() => {
- if (socket && !initialized && printerId) {
+ if (printServer && !initialized && printerId) {
setInitialized(true)
- socket.on('notify_printer_update', (statusUpdate) => {
+ printServer.on('notify_printer_update', (statusUpdate) => {
setPrinterData((prevData) => {
if (statusUpdate?._id === printerId) {
return {
@@ -158,7 +158,7 @@ const ControlPrinter = () => {
})
// Add WebSocket event listener for filament stock updates
- socket.on('notify_filamentstock_update', (filamentStockUpdate) => {
+ printServer.on('notify_filamentstock_update', (filamentStockUpdate) => {
setPrinterData((prevData) => {
if (prevData?.currentFilamentStock) {
if (
@@ -178,17 +178,17 @@ const ControlPrinter = () => {
})
}
return () => {
- if (socket && initialized) {
+ if (printServer && initialized) {
console.log('Deregistering')
- socket.off('notify_printer_update')
- socket.off('notify_filamentstock_update')
+ printServer.off('notify_printer_update')
+ printServer.off('notify_filamentstock_update')
}
}
- }, [socket, initialized, printerId])
+ }, [printServer, initialized, printerId])
function handleEmergencyStop() {
console.log('Emergency stop button clicked')
- socket.emit('printer.emergency_stop', { printerId })
+ printServer.emit('printer.emergency_stop', { printerId })
}
useEffect(() => {
@@ -328,19 +328,19 @@ const ControlPrinter = () => {
],
onClick: ({ key }) => {
if (key === 'restartHost') {
- socket.emit('printer.restart', { printerId })
+ printServer.emit('printer.restart', { printerId })
} else if (key === 'restartFirmware') {
- socket.emit('printer.firmware_restart', { printerId })
+ printServer.emit('printer.firmware_restart', { printerId })
} else if (key === 'resumePrint') {
- socket.emit('printer.print.resume', { printerId })
+ printServer.emit('printer.print.resume', { printerId })
} else if (key === 'pausePrint') {
- socket.emit('printer.print.pause', { printerId })
+ printServer.emit('printer.print.pause', { printerId })
} else if (key === 'cancelPrint') {
- socket.emit('printer.print.cancel', { printerId })
+ printServer.emit('printer.print.cancel', { printerId })
} else if (key === 'startQueue') {
- socket.emit('server.job_queue.start', { printerId })
+ printServer.emit('server.job_queue.start', { printerId })
} else if (key === 'pauseQueue') {
- socket.emit('server.job_queue.pause', { printerId })
+ printServer.emit('server.job_queue.pause', { printerId })
} else if (key === 'loadFilamentStock') {
setLoadFilamentStockModalOpen(true)
} else if (key === 'unloadFilamentStock') {
@@ -467,9 +467,9 @@ const ControlPrinter = () => {
}
onClick={() => {
if (printerData?.state?.type === 'paused') {
- socket.emit('printer.print.resume', { printerId })
+ printServer.emit('printer.print.resume', { printerId })
} else {
- socket.emit('printer.print.pause', { printerId })
+ printServer.emit('printer.print.pause', { printerId })
}
}}
>
@@ -482,7 +482,7 @@ const ControlPrinter = () => {
printerData?.state?.type === 'error'
}
onClick={() => {
- socket.emit('server.job_queue.start', { printerId })
+ printServer.emit('server.job_queue.start', { printerId })
}}
>
@@ -933,7 +933,7 @@ const ControlPrinter = () => {
key='firmwareRestart'
icon={}
onClick={() => {
- socket.emit('printer.firmware_restart', { printerId })
+ printServer.emit('printer.firmware_restart', { printerId })
setKlippyErrorModalOpen(false)
}}
>
diff --git a/src/components/Dashboard/Production/Printers/NewPrinter.jsx b/src/components/Dashboard/Production/Printers/NewPrinter.jsx
index e777570..a02cb19 100644
--- a/src/components/Dashboard/Production/Printers/NewPrinter.jsx
+++ b/src/components/Dashboard/Production/Printers/NewPrinter.jsx
@@ -21,7 +21,7 @@ import {
} from 'antd'
import { SearchOutlined, SettingOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'
-import { SocketContext } from '../../context/SocketContext'
+import { PrintServerContext } from '../../context/PrintServerContext'
import EditIcon from '../../../Icons/EditIcon.jsx'
import config from '../../../../config.js'
@@ -43,7 +43,7 @@ const NewPrinter = ({ onOk, reset }) => {
reset: PropTypes.bool.isRequired
}
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [messageApi, contextHolder] = message.useMessage()
const [notificationApi, notificationContextHolder] =
notification.useNotification()
@@ -243,22 +243,22 @@ const NewPrinter = ({ onOk, reset }) => {
setDiscovering(true)
setDiscoveredPrinters([])
messageApi.info('Discovering printers...')
- socket.off('notify_scan_network_found')
- socket.off('notify_scan_network_progress')
- socket.off('notify_scan_network_complete')
+ printServer.off('notify_scan_network_found')
+ printServer.off('notify_scan_network_progress')
+ printServer.off('notify_scan_network_complete')
- socket.on('notify_scan_network_found', notifyScanNetworkFound)
- socket.on('notify_scan_network_progress', notifyScanNetworkProgress)
- socket.on('notify_scan_network_complete', notifyScanNetworkComplete)
+ printServer.on('notify_scan_network_found', notifyScanNetworkFound)
+ printServer.on('notify_scan_network_progress', notifyScanNetworkProgress)
+ printServer.on('notify_scan_network_complete', notifyScanNetworkComplete)
- socket.emit('bridge.scan_network.start', {
+ printServer.emit('bridge.scan_network.start', {
port: scanPort,
protocol: scanProtocol
})
}
}, [
discovering,
- socket,
+ printServer,
scanPort,
scanProtocol,
messageApi,
@@ -279,10 +279,10 @@ const NewPrinter = ({ onOk, reset }) => {
setDiscovering(false)
notificationApi.destroy('network-scan')
messageApi.info('Stopping discovery...')
- socket.off('notify_scan_network_found')
- socket.off('notify_scan_network_progress')
- socket.off('notify_scan_network_complete')
- socket.emit('bridge.scan_network.stop', (response) => {
+ printServer.off('notify_scan_network_found')
+ printServer.off('notify_scan_network_progress')
+ printServer.off('notify_scan_network_complete')
+ printServer.emit('bridge.scan_network.stop', (response) => {
if (response == false) {
messageApi.error('Error stopping discovery!')
}
diff --git a/src/components/Dashboard/common/DashboardBreadcrumb.jsx b/src/components/Dashboard/common/DashboardBreadcrumb.jsx
index 01db842..bee7906 100644
--- a/src/components/Dashboard/common/DashboardBreadcrumb.jsx
+++ b/src/components/Dashboard/common/DashboardBreadcrumb.jsx
@@ -46,7 +46,7 @@ const breadcrumbNameMap = {
'/dashboard/inventory/stockaudits/info': 'Info',
'/dashboard/developer/sessionstorage': 'Session Storage',
'/dashboard/developer/authcontextdebug': 'Auth Context Debug',
- '/dashboard/developer/socketcontextdebug': 'Socket Context Debug'
+ '/dashboard/developer/printservercontextdebug': 'Print Server Context Debug'
}
const DashboardBreadcrumb = () => {
diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx
index 19acece..3e3b8a5 100644
--- a/src/components/Dashboard/common/DashboardNavigation.jsx
+++ b/src/components/Dashboard/common/DashboardNavigation.jsx
@@ -8,9 +8,8 @@ import {
Dropdown,
Button,
Tooltip,
- Typography,
- Divider,
- Badge
+ Badge,
+ Divider
} from 'antd'
import {
LogoutOutlined,
@@ -19,12 +18,13 @@ import {
LoadingOutlined
} from '@ant-design/icons'
import { AuthContext } from '../context/AuthContext'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import { SpotlightContext } from '../context/SpotlightContext'
-import { NotificationContext } from '../context/NotificationContext'
+import { ApiServerContext } from '../context/ApiServerContext'
import { useNavigate, useLocation } from 'react-router-dom'
import { Header } from 'antd/es/layout/layout'
import { useMediaQuery } from 'react-responsive'
+import KeyboardShortcut from './KeyboardShortcut'
import FarmControlLogo from '../../Logos/FarmControlLogo'
import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
@@ -36,16 +36,16 @@ import BellIcon from '../../Icons/BellIcon'
import SearchIcon from '../../Icons/SearchIcon'
import SettingsIcon from '../../Icons/SettingsIcon'
import DeveloperIcon from '../../Icons/DeveloperIcon'
-
-const { Text } = Typography
+import PrinterIcon from '../../Icons/PrinterIcon'
const DashboardNavigation = () => {
const { logout, userProfile } = useContext(AuthContext)
const { showSpotlight } = useContext(SpotlightContext)
- const { toggleNotificationCenter, unreadCount } =
- useContext(NotificationContext)
- const { socket } = useContext(SocketContext)
- const [socketState, setSocketState] = useState('disconnected')
+ const { toggleNotificationCenter, unreadCount } = useContext(ApiServerContext)
+ const { printServer } = useContext(PrintServerContext)
+ const { apiServer } = useContext(ApiServerContext)
+ const [printServerState, setPrintServerState] = useState('disconnected')
+ const [apiServerState, setApiServerState] = useState('disconnected')
const navigate = useNavigate()
const location = useLocation()
const [selectedKey, setSelectedKey] = useState('production')
@@ -59,14 +59,24 @@ const DashboardNavigation = () => {
}, [location.pathname])
useEffect(() => {
- if (socket?.connecting) {
- setSocketState('connecting')
- } else if (socket?.connected) {
- setSocketState('connected')
+ if (printServer?.connecting) {
+ setPrintServerState('connecting')
+ } else if (printServer?.connected) {
+ setPrintServerState('connected')
} 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 = [
{
@@ -165,54 +175,90 @@ const DashboardNavigation = () => {
/>
- ⌘ ⇧ P} arrow={false}>
+ showSpotlight()}
+ >
}
type='text'
style={{ marginTop: '2px' }}
onClick={() => showSpotlight()}
- >
-
+ />
+
- }
- type='text'
- style={{ marginTop: '2px' }}
- onClick={toggleNotificationCenter}
- >
+ toggleNotificationCenter()}
+ >
+ }
+ type='text'
+ style={{ marginTop: '2px' }}
+ onClick={() => toggleNotificationCenter()}
+ />
+
-
- {socketState === 'connected' ? (
-
- }
- />
-
- ) : null}
- {socketState === 'connecting' ? (
-
- }
- />
-
- ) : null}
- {socketState === 'disconnected' ? (
-
- }
- />
-
- ) : null}
-
+
{process.env.NODE_ENV === 'development' && (
+ {printServerState === 'connected' ? (
+
+ }
+ />
+
+ ) : null}
+ {printServerState === 'connecting' ? (
+
+ }
+ />
+
+ ) : null}
+ {printServerState === 'disconnected' ? (
+
+ }
+ />
+
+ ) : null}
+ {apiServerState === 'connected' ? (
+
+ }
+ />
+
+ ) : null}
+ {apiServerState === 'connecting' ? (
+
+ }
+ />
+
+ ) : null}
+ {apiServerState === 'disconnected' ? (
+
+ }
+ />
+
+ ) : null}
{
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [badgeStatus, setBadgeStatus] = useState('unknown')
const [badgeText, setBadgeText] = useState('Unknown')
const [currentState, setCurrentState] = useState(
@@ -37,20 +37,20 @@ const FilamentStockState = ({
const [initialized, setInitialized] = useState(false)
useEffect(() => {
- if (socket && !initialized && filamentStock?._id) {
+ if (printServer && !initialized && filamentStock?._id) {
setInitialized(true)
- socket.on('notify_filamentstock_update', (statusUpdate) => {
+ printServer.on('notify_filamentstock_update', (statusUpdate) => {
if (statusUpdate?._id === filamentStock?._id && statusUpdate?.state) {
setCurrentState(statusUpdate.state)
}
})
}
return () => {
- if (socket && initialized) {
- socket.off('notify_filamentstock_update')
+ if (printServer && initialized) {
+ printServer.off('notify_filamentstock_update')
}
}
- }, [socket, initialized, filamentStock?._id])
+ }, [printServer, initialized, filamentStock?._id])
useEffect(() => {
switch (currentState.type) {
diff --git a/src/components/Dashboard/common/InfoCollapse.jsx b/src/components/Dashboard/common/InfoCollapse.jsx
new file mode 100644
index 0000000..5da5a59
--- /dev/null
+++ b/src/components/Dashboard/common/InfoCollapse.jsx
@@ -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 (
+ onToggle(keys.length > 0)}
+ expandIcon={({ isActive }) => (
+
+ )}
+ className={`no-h-padding-collapse ${className}`}
+ >
+
+ {icon}
+
+ {title}
+
+
+ }
+ key={key}
+ >
+ {children}
+
+
+ )
+}
+
+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
diff --git a/src/components/Dashboard/common/JobState.jsx b/src/components/Dashboard/common/JobState.jsx
index 96afec5..6fbac4e 100644
--- a/src/components/Dashboard/common/JobState.jsx
+++ b/src/components/Dashboard/common/JobState.jsx
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'
import { Progress, Flex, Typography, Space } from 'antd'
import React, { useState, useContext, useEffect } from 'react'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import IdText from './IdText'
import StateTag from './StateTag'
@@ -12,7 +12,7 @@ const JobState = ({
showId = true,
showQuantity = true
}) => {
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [currentState, setCurrentState] = useState(
job?.state || { type: 'unknown', progress: 0 }
)
@@ -20,20 +20,20 @@ const JobState = ({
const { Text } = Typography
useEffect(() => {
- if (socket && !initialized && job?._id) {
+ if (printServer && !initialized && job?._id) {
setInitialized(true)
- socket.on('notify_job_update', (statusUpdate) => {
+ printServer.on('notify_job_update', (statusUpdate) => {
if (statusUpdate?._id === job._id && statusUpdate?.state) {
setCurrentState(statusUpdate.state)
}
})
}
return () => {
- if (socket && initialized) {
- socket.off('notify_job_update')
+ if (printServer && initialized) {
+ printServer.off('notify_job_update')
}
}
- }, [socket, initialized, job?._id])
+ }, [printServer, initialized, job?._id])
return (
diff --git a/src/components/Dashboard/common/KeyboardShortcut.jsx b/src/components/Dashboard/common/KeyboardShortcut.jsx
new file mode 100644
index 0000000..3aaccab
--- /dev/null
+++ b/src/components/Dashboard/common/KeyboardShortcut.jsx
@@ -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 (
+
+ {hint}
+
+ }
+ arrow={false}
+ >
+ {element}
+
+ )
+ }
+ 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
diff --git a/src/components/Dashboard/common/PrinterJobsTree.jsx b/src/components/Dashboard/common/PrinterJobsTree.jsx
index 55ab5e3..e737e59 100644
--- a/src/components/Dashboard/common/PrinterJobsTree.jsx
+++ b/src/components/Dashboard/common/PrinterJobsTree.jsx
@@ -4,7 +4,7 @@ import { LoadingOutlined } from '@ant-design/icons'
import React, { useState, useEffect, useContext } from 'react'
import { useNavigate } from 'react-router-dom'
import SubJobState from './SubJobState'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import axios from 'axios'
import JobState from './JobState'
import JobIcon from '../../Icons/JobIcon'
@@ -20,7 +20,7 @@ const PrinterJobsTree = ({
const [subJobs, setSubJobs] = useState(initialSubJobs || [])
const [treeLoading, setTreeLoading] = useState(initialLoading)
const [error, setError] = useState(null)
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [messageApi] = message.useMessage()
const [expandedKeys, setExpandedKeys] = useState([])
const [treeData, setTreeData] = useState([])
@@ -116,9 +116,9 @@ const PrinterJobsTree = ({
initializeData()
- // Add socket.io event listener for subjob updates
- if (socket) {
- socket.on('notify_subjob_update', (updateData) => {
+ // Add printServer.io event listener for subjob updates
+ if (printServer) {
+ printServer.on('notify_subjob_update', (updateData) => {
if (updateData.subJobId) {
setSubJobs((prevSubJobs) =>
prevSubJobs.map((subJob) => {
@@ -137,11 +137,11 @@ const PrinterJobsTree = ({
}
return () => {
- if (socket) {
- socket.off('notify_subjob_update')
+ if (printServer) {
+ printServer.off('notify_subjob_update')
}
}
- }, [initialSubJobs, socket])
+ }, [initialSubJobs, printServer])
if (error) {
return (
diff --git a/src/components/Dashboard/common/PrinterMiscPanel.jsx b/src/components/Dashboard/common/PrinterMiscPanel.jsx
index a28e4fe..914acdd 100644
--- a/src/components/Dashboard/common/PrinterMiscPanel.jsx
+++ b/src/components/Dashboard/common/PrinterMiscPanel.jsx
@@ -1,7 +1,7 @@
import React, { useContext, useState, useEffect } from 'react'
import { Typography, Spin, Flex, Space, Slider, Descriptions, Tag } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import PropTypes from 'prop-types'
const { Text } = Typography
@@ -33,7 +33,7 @@ const PrinterMiscPanel = ({
})
const [initialized, setInitialized] = useState(false)
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [fanSpeed, setFanSpeed] = useState(0)
const [ledBrightness, setLedBrightness] = useState(0)
const [beeperValue, setBeeperValue] = useState(0)
@@ -89,30 +89,30 @@ const PrinterMiscPanel = ({
}
}
- if (!initialized && socket.connected) {
+ if (!initialized && printServer.connected) {
setInitialized(true)
- socket.on('connect', () => {
- socket.emit('printer.objects.subscribe', params)
- socket.emit('printer.objects.query', params)
+ printServer.on('connect', () => {
+ printServer.emit('printer.objects.subscribe', params)
+ printServer.emit('printer.objects.query', params)
})
- socket.emit('printer.objects.subscribe', params)
- socket.emit('printer.objects.query', params)
- socket.on('notify_status_update', notifyMiscStatusUpdate)
+ printServer.emit('printer.objects.subscribe', params)
+ printServer.emit('printer.objects.query', params)
+ printServer.on('notify_status_update', notifyMiscStatusUpdate)
}
return () => {
- if (socket.connected && initialized && shouldUnsubscribe) {
- socket.off('notify_status_update', notifyMiscStatusUpdate)
- socket.emit('printer.objects.unsubscribe', params)
+ if (printServer.connected && initialized && shouldUnsubscribe) {
+ printServer.off('notify_status_update', notifyMiscStatusUpdate)
+ printServer.emit('printer.objects.unsubscribe', params)
}
}
- }, [socket, initialized, printerId, shouldUnsubscribe])
+ }, [printServer, initialized, printerId, shouldUnsubscribe])
const handleSetFanSpeed = (value) => {
- if (socket) {
- socket.emit('printer.gcode.script', {
+ if (printServer) {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `M106 S${Math.round(value * 255)}`
})
@@ -120,8 +120,8 @@ const PrinterMiscPanel = ({
}
const handleSetLedBrightness = (value) => {
- if (socket) {
- socket.emit('printer.gcode.script', {
+ if (printServer) {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `SET_LED LED=led_backlight BRIGHTNESS=${value}`
})
@@ -129,8 +129,8 @@ const PrinterMiscPanel = ({
}
const handleSetBeeperValue = (value) => {
- if (socket) {
- socket.emit('printer.gcode.script', {
+ if (printServer) {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `M300 S440 P200 V${Math.round(value * 100)}`
})
diff --git a/src/components/Dashboard/common/PrinterMovementPanel.jsx b/src/components/Dashboard/common/PrinterMovementPanel.jsx
index 440ebf2..0e35f5f 100644
--- a/src/components/Dashboard/common/PrinterMovementPanel.jsx
+++ b/src/components/Dashboard/common/PrinterMovementPanel.jsx
@@ -10,7 +10,7 @@ import {
Card,
message // eslint-disable-line
} from 'antd'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import PropTypes from 'prop-types'
import LevelBedIcon from '../../Icons/LevelBedIcon'
import ArrowLeftIcon from '../../Icons/ArrowLeftIcon'
@@ -22,7 +22,7 @@ import HomeIcon from '../../Icons/HomeIcon'
const PrinterMovementPanel = ({ printerId }) => {
const [posValue, setPosValue] = useState(10)
const [rateValue, setRateValue] = useState(1000)
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
//const messageApi = message.useMessage()
@@ -40,9 +40,9 @@ const PrinterMovementPanel = ({ printerId }) => {
}
const handleHomeAxisClick = (axis) => {
- if (socket) {
+ if (printServer) {
console.log('Homeing Axis:', axis)
- socket.emit('printer.gcode.script', {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `G28 ${axis}`
})
@@ -51,9 +51,9 @@ const PrinterMovementPanel = ({ printerId }) => {
const handleMoveAxisClick = (axis, minus) => {
const distanceValue = !minus ? posValue * -1 : posValue
- if (socket) {
+ if (printServer) {
console.log('Moving Axis:', axis, distanceValue)
- socket.emit('printer.gcode.script', {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `_CLIENT_LINEAR_MOVE ${axis}=${distanceValue} F=${rateValue}`
})
diff --git a/src/components/Dashboard/common/PrinterPositionPanel.jsx b/src/components/Dashboard/common/PrinterPositionPanel.jsx
index 4c5cda3..fd3ffb5 100644
--- a/src/components/Dashboard/common/PrinterPositionPanel.jsx
+++ b/src/components/Dashboard/common/PrinterPositionPanel.jsx
@@ -10,7 +10,7 @@ import {
Button
} from 'antd'
import { LoadingOutlined, CaretLeftOutlined } from '@ant-design/icons'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import styled from 'styled-components'
import PropTypes from 'prop-types'
import BoolDisplay from './BoolDisplay'
@@ -46,7 +46,7 @@ const PrinterPositionPanel = ({
})
const [initialized, setInitialized] = useState(false)
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [speedFactor, setSpeedFactor] = useState(positionData.speed_factor)
const [extrudeFactor, setExtrudeFactor] = useState(
positionData.extrude_factor
@@ -76,33 +76,33 @@ const PrinterPositionPanel = ({
}
}
- if (!initialized && socket?.connected) {
+ if (!initialized && printServer?.connected) {
setInitialized(true)
- socket.on('connect', () => {
- socket.emit('printer.objects.subscribe', params)
- socket.emit('printer.objects.query', params)
+ printServer.on('connect', () => {
+ printServer.emit('printer.objects.subscribe', params)
+ printServer.emit('printer.objects.query', params)
})
- socket.emit('printer.objects.subscribe', params)
- socket.emit('printer.objects.query', params)
- socket.on('notify_status_update', notifyPositionStatusUpdate)
+ printServer.emit('printer.objects.subscribe', params)
+ printServer.emit('printer.objects.query', params)
+ printServer.on('notify_status_update', notifyPositionStatusUpdate)
}
setSpeedFactor(positionData.speed_factor)
setExtrudeFactor(positionData.extrude_factor)
return () => {
- if (socket?.connected && initialized && shouldUnsubscribe) {
- socket.off('notify_status_update', notifyPositionStatusUpdate)
- socket.emit('printer.objects.unsubscribe', params)
+ if (printServer?.connected && initialized && shouldUnsubscribe) {
+ printServer.off('notify_status_update', notifyPositionStatusUpdate)
+ printServer.emit('printer.objects.unsubscribe', params)
}
}
- }, [socket, initialized, printerId, shouldUnsubscribe])
+ }, [printServer, initialized, printerId, shouldUnsubscribe])
const handleSetSpeedFactor = () => {
- if (socket) {
- socket.emit('printer.gcode.script', {
+ if (printServer) {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `M220 S${speedFactor * 100}`
})
@@ -110,8 +110,8 @@ const PrinterPositionPanel = ({
}
const handleSetExtrudeFactor = () => {
- if (socket) {
- socket.emit('printer.gcode.script', {
+ if (printServer) {
+ printServer.emit('printer.gcode.script', {
printerId,
script: `M221 S${extrudeFactor * 100}`
})
diff --git a/src/components/Dashboard/common/PrinterState.jsx b/src/components/Dashboard/common/PrinterState.jsx
index df93d49..6bbc0b0 100644
--- a/src/components/Dashboard/common/PrinterState.jsx
+++ b/src/components/Dashboard/common/PrinterState.jsx
@@ -2,7 +2,7 @@
import PropTypes from 'prop-types'
import { Progress, Flex, Space, Typography, Button, Tooltip } from 'antd'
import React, { useState, useContext, useEffect } from 'react'
-import { SocketContext } from '../context/SocketContext'
+import { PrintServerContext } from '../context/PrintServerContext'
import { CaretLeftOutlined } from '@ant-design/icons'
import XMarkIcon from '../../Icons/XMarkIcon'
import PauseIcon from '../../Icons/PauseIcon'
@@ -15,7 +15,7 @@ const PrinterState = ({
showPrinterName = true,
showControls = true
}) => {
- const { socket } = useContext(SocketContext)
+ const { printServer } = useContext(PrintServerContext)
const [currentState, setCurrentState] = useState(
printer?.state || {
type: 'unknown',
@@ -26,20 +26,20 @@ const PrinterState = ({
const { Text } = Typography
useEffect(() => {
- if (socket && !initialized && printer?.id) {
+ if (printServer && !initialized && printer?.id) {
setInitialized(true)
- socket.on('notify_printer_update', (statusUpdate) => {
+ printServer.on('notify_printer_update', (statusUpdate) => {
if (statusUpdate?._id === printer.id && statusUpdate?.state) {
setCurrentState(statusUpdate.state)
}
})
}
return () => {
- if (socket && initialized) {
- socket.off('notify_printer_update')
+ if (printServer && initialized) {
+ printServer.off('notify_printer_update')
}
}
- }, [socket, initialized, printer?.id])
+ }, [printServer, initialized, printer?.id])
return (
@@ -67,11 +67,11 @@ const PrinterState = ({