From 12c5cd83f4b459140a02e126fe9dfbed026e40eb Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 7 Nov 2026 22:48:37 +0000 Subject: [PATCH] Enhance Slicer Integration with Object Update Subscription - 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. --- .../Production/Printers/SlicerIntegration.jsx | 35 +++- .../Dashboard/context/ApiServerContext.jsx | 193 ++++++++++++------ 2 files changed, 167 insertions(+), 61 deletions(-) diff --git a/src/components/Dashboard/Production/Printers/SlicerIntegration.jsx b/src/components/Dashboard/Production/Printers/SlicerIntegration.jsx index f50a4d47..e6dd2cf2 100644 --- a/src/components/Dashboard/Production/Printers/SlicerIntegration.jsx +++ b/src/components/Dashboard/Production/Printers/SlicerIntegration.jsx @@ -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 diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx index 830f94bb..6570673e 100644 --- a/src/components/Dashboard/context/ApiServerContext.jsx +++ b/src/components/Dashboard/context/ApiServerContext.jsx @@ -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] )