Enhance Slicer Integration with Object Update Subscription
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- Added subscription to object updates for printers, allowing real-time updates of pending slicer uploads.
- Introduced a reference to prevent redundant updates when new data is received.
- Updated the ApiServerContext to manage object update subscriptions, improving data consistency and responsiveness in the SlicerIntegration component.
This commit is contained in:
Tom Butcher 2026-11-07 22:48:37 +00:00
parent 2baf9cffab
commit 12c5cd83f4
2 changed files with 167 additions and 61 deletions

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react'
import { useContext, useEffect, useRef, useState } from 'react'
import { Divider, Flex, Layout, Typography, Button, Badge, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import { useLocation } from 'react-router-dom'
@ -18,7 +18,9 @@ const { Title } = Typography
const SlicerIntegration = () => {
const location = useLocation()
const printerId = new URLSearchParams(location.search).get('printerId')
const { updateObject } = useContext(ApiServerContext)
const { updateObject, subscribeToObjectUpdates } =
useContext(ApiServerContext)
const receivedPendingUploadsUpdateRef = useRef(false)
const [pendingSlicerUploads, setPendingSlicerUploads] = useState([])
const [currentPendingSlicerUpload, setCurrentPendingSlicerUpload] =
@ -43,11 +45,40 @@ const SlicerIntegration = () => {
const [showSlicerUploadFlow, setShowSlicerUploadFlow] = useState(false)
useEffect(() => {
if (receivedPendingUploadsUpdateRef.current) {
return
}
setPendingSlicerUploads(
objectFormState.objectData?.pendingSlicerUploads || []
)
}, [objectFormState.objectData?.pendingSlicerUploads])
useEffect(() => {
if (!printerId) {
return undefined
}
const unsubscribe = subscribeToObjectUpdates(
String(printerId).toLowerCase(),
'printer',
(object) => {
if (
!object ||
!Object.prototype.hasOwnProperty.call(object, 'pendingSlicerUploads')
) {
return
}
receivedPendingUploadsUpdateRef.current = true
setPendingSlicerUploads(object.pendingSlicerUploads || [])
}
)
return () => {
if (unsubscribe) unsubscribe()
}
}, [printerId, subscribeToObjectUpdates])
useEffect(() => {
const nextUpload = pendingSlicerUploads.find(
(upload) => upload.new === true

View File

@ -396,6 +396,8 @@ const ApiServerProvider = ({ children }) => {
const [userSettingsLoaded, setUserSettingsLoaded] = useState(false)
const subscribedCallbacksRef = useRef(new Map())
const subscribedActivityCallbacksRef = useRef(new Map())
const subscribedObjectUpdateCallbacksRef = useRef(new Map())
const subscribedObjectUpdateServerRef = useRef(new Set())
const subscribedActivityServerSubscriptionsRef = useRef(new Set())
const notificationListenersRef = useRef(new Set())
const [connectionIssue, setConnectionIssue] = useState(false)
@ -445,6 +447,7 @@ const ApiServerProvider = ({ children }) => {
const clearSubscriptions = useCallback(() => {
subscribedCallbacksRef.current.clear()
subscribedActivityServerSubscriptionsRef.current.clear()
subscribedObjectUpdateServerRef.current.clear()
notificationListenersRef.current.clear()
}, [])
@ -505,6 +508,45 @@ const ApiServerProvider = ({ children }) => {
[]
)
const resubscribeObjectUpdateListeners = useCallback(
(socket = socketRef.current) => {
if (!socket?.connected) {
return
}
subscribedObjectUpdateCallbacksRef.current.forEach(
(callbacks, callbacksRefKey) => {
if (!callbacks?.length) {
return
}
if (subscribedObjectUpdateServerRef.current.has(callbacksRefKey)) {
return
}
const colonIndex = callbacksRefKey.indexOf(':')
if (colonIndex === -1) {
return
}
const objectType = callbacksRefKey.slice(0, colonIndex)
const objectId = callbacksRefKey.slice(colonIndex + 1)
subscribedObjectUpdateServerRef.current.add(callbacksRefKey)
socket.emit(
'subscribeToObjectUpdate',
{ _id: objectId, objectType },
(result) => {
if (!result?.success) {
subscribedObjectUpdateServerRef.current.delete(callbacksRefKey)
}
}
)
}
)
},
[]
)
const registerNotificationListener = useCallback((callback) => {
notificationListenersRef.current.add(callback)
return () => notificationListenersRef.current.delete(callback)
@ -729,6 +771,7 @@ const ApiServerProvider = ({ children }) => {
setConnectionIssue(false)
resetReconnectState()
resubscribeActivityListeners(newSocket)
resubscribeObjectUpdateListeners(newSocket)
})
})
@ -797,7 +840,8 @@ const ApiServerProvider = ({ children }) => {
getUserSettings,
resubscribeActivityListeners,
resetReconnectState,
markConnectionLost
markConnectionLost,
resubscribeObjectUpdateListeners
])
connectToServerRef.current = connectToServer
@ -861,6 +905,10 @@ const ApiServerProvider = ({ children }) => {
subscribedActivityServerSubscriptionsRef.current.clear()
resubscribeActivityListeners(socket)
}
if (socket && subscribedObjectUpdateCallbacksRef.current.size > 0) {
subscribedObjectUpdateServerRef.current.clear()
resubscribeObjectUpdateListeners(socket)
}
}
const handleWindowFocus = () => {
@ -874,6 +922,7 @@ const ApiServerProvider = ({ children }) => {
logger.debug('Page restored from bfcache, reconnecting socket...')
subscribedActivityServerSubscriptionsRef.current.clear()
subscribedObjectUpdateServerRef.current.clear()
setConnected(false)
teardownSocket()
connectToServer()
@ -893,6 +942,7 @@ const ApiServerProvider = ({ children }) => {
authenticated,
connectToServer,
resubscribeActivityListeners,
resubscribeObjectUpdateListeners,
attemptReconnect,
teardownSocket
])
@ -950,7 +1000,21 @@ const ApiServerProvider = ({ children }) => {
const callbacksRefKey = `${objectType}:${id}`
const allCallbacksRefKey = getAllObjectUpdatesSubscriptionKey(objectType)
if (
const objectUpdateCallbacks =
subscribedObjectUpdateCallbacksRef.current.get(callbacksRefKey)
if (id && objectType && objectUpdateCallbacks?.length) {
logger.debug(
`Calling ${objectUpdateCallbacks.length} callbacks for object:`,
callbacksRefKey
)
objectUpdateCallbacks.forEach((callback) => {
try {
callback(data.object)
} catch (error) {
logger.error('Error in object update callback:', error)
}
})
} else if (
id &&
objectType &&
subscribedCallbacksRef.current.has(callbacksRefKey)
@ -1106,28 +1170,31 @@ const ApiServerProvider = ({ children }) => {
}
const offObjectUpdatesEvent = useCallback((id, objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}`
// Remove callback from the subscribed callbacks map
if (subscribedCallbacksRef.current.has(callbacksRefKey)) {
const callbacks = subscribedCallbacksRef.current
.get(callbacksRefKey)
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
logger.debug(
'No callbacks found for object:',
callbacksRefKey,
'unsubscribing from object update...'
)
subscribedCallbacksRef.current.delete(callbacksRefKey)
socketRef.current.emit('unsubscribeObjectUpdate', {
_id: id,
objectType: objectType
})
} else {
subscribedCallbacksRef.current.set(callbacksRefKey, callbacks)
}
const callbacksRefKey = `${objectType}:${id}`
if (!subscribedObjectUpdateCallbacksRef.current.has(callbacksRefKey)) {
return
}
const callbacks = subscribedObjectUpdateCallbacksRef.current
.get(callbacksRefKey)
.filter((cb) => cb !== callback)
if (callbacks.length === 0) {
subscribedObjectUpdateCallbacksRef.current.delete(callbacksRefKey)
subscribedObjectUpdateServerRef.current.delete(callbacksRefKey)
if (socketRef.current?.connected) {
logger.debug(
'No callbacks found for object:',
callbacksRefKey,
'unsubscribing from object update...'
)
socketRef.current.emit('unsubscribeObjectUpdate', {
_id: id,
objectType
})
}
} else {
subscribedObjectUpdateCallbacksRef.current.set(callbacksRefKey, callbacks)
}
}, [])
@ -1181,43 +1248,51 @@ const ApiServerProvider = ({ children }) => {
const subscribeToObjectUpdates = useCallback(
(id, objectType, callback) => {
if (socketRef.current && socketRef.current.connected == true) {
const callbacksRefKey = `${objectType}:${id}`
// Add callback to the subscribed callbacks map immediately
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
subscribedCallbacksRef.current.set(callbacksRefKey, [])
}
const callbacksLength =
subscribedCallbacksRef.current.get(callbacksRefKey).length
if (callbacksLength <= 0) {
socketRef.current.emit(
'subscribeToObjectUpdate',
{
_id: id,
objectType: objectType
},
(result) => {
if (result.success) {
logger.info('Subscribed to id:', id, 'objectType:', objectType)
}
}
)
}
logger.info(
'Adding callback id:',
id,
'objectType:',
objectType,
'callbacks length:',
callbacksLength + 1
)
subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
// Return cleanup function
return () => offObjectUpdatesEvent(id, objectType, callback)
if (!id || !objectType || typeof callback !== 'function') {
return undefined
}
const callbacksRefKey = `${objectType}:${id}`
if (!subscribedObjectUpdateCallbacksRef.current.has(callbacksRefKey)) {
subscribedObjectUpdateCallbacksRef.current.set(callbacksRefKey, [])
}
const callbacks =
subscribedObjectUpdateCallbacksRef.current.get(callbacksRefKey)
callbacks.push(callback)
const socket = socketRef.current
if (
socket?.connected &&
!subscribedObjectUpdateServerRef.current.has(callbacksRefKey)
) {
subscribedObjectUpdateServerRef.current.add(callbacksRefKey)
socket.emit(
'subscribeToObjectUpdate',
{
_id: id,
objectType
},
(result) => {
if (result?.success) {
logger.info('Subscribed to id:', id, 'objectType:', objectType)
} else {
subscribedObjectUpdateServerRef.current.delete(callbacksRefKey)
}
}
)
}
logger.info(
'Adding callback id:',
id,
'objectType:',
objectType,
'callbacks length:',
callbacks.length
)
return () => offObjectUpdatesEvent(id, objectType, callback)
},
[offObjectUpdatesEvent]
)