mirror of
https://github.com/jhbruhn/respira.git
synced 2026-03-13 18:28:41 +00:00
Resolved all 7 issues identified in PR review: 1. @testing-library/dom peer dependency already explicitly listed 2. Removed invalid eslint-disable comments (replaced with correct rule) 3. Fixed unstable callbacks in useMachinePolling using refs to prevent unnecessary re-renders 4. Fixed useAutoScroll options dependency with useMemo for stability 5. Fixed stale closure in BluetoothDevicePicker using functional setState 6. Fixed memory leak in useBluetoothDeviceListener by preventing re-registration of IPC listeners 7. Added proper eslint-disable for intentional setState in effect with detailed comment All tests passing (91/91), build successful, linter clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
186 lines
5.7 KiB
TypeScript
186 lines
5.7 KiB
TypeScript
/**
|
|
* useMachinePolling Hook
|
|
*
|
|
* Implements dynamic polling for machine status based on machine state.
|
|
* Uses adaptive polling intervals and conditional progress polling during sewing.
|
|
*
|
|
* Polling intervals:
|
|
* - 500ms for active states (SEWING, MASK_TRACING, SEWING_DATA_RECEIVE)
|
|
* - 1000ms for waiting states (COLOR_CHANGE_WAIT, MASK_TRACE_LOCK_WAIT, SEWING_WAIT)
|
|
* - 2000ms for idle/other states
|
|
*
|
|
* Additionally polls service count every 10 seconds.
|
|
*
|
|
* @param options - Configuration options
|
|
* @param options.machineStatus - Current machine status to determine polling interval
|
|
* @param options.patternInfo - Current pattern info for resumable pattern check
|
|
* @param options.onStatusRefresh - Callback to refresh machine status
|
|
* @param options.onProgressRefresh - Callback to refresh sewing progress
|
|
* @param options.onServiceCountRefresh - Callback to refresh service count
|
|
* @param options.onPatternInfoRefresh - Callback to refresh pattern info
|
|
* @param options.shouldCheckResumablePattern - Function to check if resumable pattern exists
|
|
* @returns Object containing start/stop functions and polling state
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { startPolling, stopPolling, isPolling } = useMachinePolling({
|
|
* machineStatus,
|
|
* patternInfo,
|
|
* onStatusRefresh: async () => { ... },
|
|
* onProgressRefresh: async () => { ... },
|
|
* onServiceCountRefresh: async () => { ... },
|
|
* onPatternInfoRefresh: async () => { ... },
|
|
* shouldCheckResumablePattern: () => resumeAvailable
|
|
* });
|
|
*
|
|
* useEffect(() => {
|
|
* startPolling();
|
|
* return () => stopPolling();
|
|
* }, []);
|
|
* ```
|
|
*/
|
|
|
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
import { MachineStatus } from "../../types/machine";
|
|
import type { PatternInfo } from "../../types/machine";
|
|
|
|
export interface UseMachinePollingOptions {
|
|
machineStatus: MachineStatus;
|
|
patternInfo: PatternInfo | null;
|
|
onStatusRefresh: () => Promise<void>;
|
|
onProgressRefresh: () => Promise<void>;
|
|
onServiceCountRefresh: () => Promise<void>;
|
|
onPatternInfoRefresh: () => Promise<void>;
|
|
shouldCheckResumablePattern: () => boolean;
|
|
}
|
|
|
|
export interface UseMachinePollingReturn {
|
|
startPolling: () => void;
|
|
stopPolling: () => void;
|
|
isPolling: boolean;
|
|
}
|
|
|
|
export function useMachinePolling(
|
|
options: UseMachinePollingOptions,
|
|
): UseMachinePollingReturn {
|
|
const {
|
|
machineStatus,
|
|
patternInfo,
|
|
onStatusRefresh,
|
|
onProgressRefresh,
|
|
onServiceCountRefresh,
|
|
onPatternInfoRefresh,
|
|
shouldCheckResumablePattern,
|
|
} = options;
|
|
|
|
const [isPolling, setIsPolling] = useState(false);
|
|
const pollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
const serviceCountIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
const pollFunctionRef = useRef<(() => Promise<void>) | undefined>(undefined);
|
|
|
|
// Store callbacks in refs to avoid unnecessary re-renders
|
|
const callbacksRef = useRef({
|
|
onStatusRefresh,
|
|
onProgressRefresh,
|
|
onServiceCountRefresh,
|
|
onPatternInfoRefresh,
|
|
shouldCheckResumablePattern,
|
|
});
|
|
|
|
// Update refs when callbacks change
|
|
useEffect(() => {
|
|
callbacksRef.current = {
|
|
onStatusRefresh,
|
|
onProgressRefresh,
|
|
onServiceCountRefresh,
|
|
onPatternInfoRefresh,
|
|
shouldCheckResumablePattern,
|
|
};
|
|
});
|
|
|
|
// Function to determine polling interval based on machine status
|
|
const getPollInterval = useCallback((status: MachineStatus) => {
|
|
// Fast polling for active states
|
|
if (
|
|
status === MachineStatus.SEWING ||
|
|
status === MachineStatus.MASK_TRACING ||
|
|
status === MachineStatus.SEWING_DATA_RECEIVE
|
|
) {
|
|
return 500;
|
|
} else if (
|
|
status === MachineStatus.COLOR_CHANGE_WAIT ||
|
|
status === MachineStatus.MASK_TRACE_LOCK_WAIT ||
|
|
status === MachineStatus.SEWING_WAIT
|
|
) {
|
|
return 1000;
|
|
}
|
|
return 2000; // Default for idle states
|
|
}, []);
|
|
|
|
// Main polling function
|
|
const poll = useCallback(async () => {
|
|
await callbacksRef.current.onStatusRefresh();
|
|
|
|
// Refresh progress during sewing
|
|
if (machineStatus === MachineStatus.SEWING) {
|
|
await callbacksRef.current.onProgressRefresh();
|
|
}
|
|
|
|
// Check if we have a cached pattern and pattern info needs refreshing
|
|
// This follows the app's logic for resumable patterns
|
|
if (
|
|
callbacksRef.current.shouldCheckResumablePattern() &&
|
|
patternInfo?.totalStitches === 0
|
|
) {
|
|
await callbacksRef.current.onPatternInfoRefresh();
|
|
}
|
|
|
|
// Schedule next poll with updated interval
|
|
const newInterval = getPollInterval(machineStatus);
|
|
if (pollFunctionRef.current) {
|
|
pollTimeoutRef.current = setTimeout(pollFunctionRef.current, newInterval);
|
|
}
|
|
}, [machineStatus, patternInfo, getPollInterval]);
|
|
|
|
// Store poll function in ref for recursive setTimeout
|
|
useEffect(() => {
|
|
pollFunctionRef.current = poll;
|
|
}, [poll]);
|
|
|
|
const stopPolling = useCallback(() => {
|
|
if (pollTimeoutRef.current) {
|
|
clearTimeout(pollTimeoutRef.current);
|
|
pollTimeoutRef.current = null;
|
|
}
|
|
|
|
if (serviceCountIntervalRef.current) {
|
|
clearInterval(serviceCountIntervalRef.current);
|
|
serviceCountIntervalRef.current = null;
|
|
}
|
|
|
|
setIsPolling(false);
|
|
}, []);
|
|
|
|
const startPolling = useCallback(() => {
|
|
// Stop any existing polling
|
|
stopPolling();
|
|
|
|
// Start main polling
|
|
const initialInterval = getPollInterval(machineStatus);
|
|
pollTimeoutRef.current = setTimeout(poll, initialInterval);
|
|
|
|
// Start service count polling (every 10 seconds)
|
|
serviceCountIntervalRef.current = setInterval(
|
|
callbacksRef.current.onServiceCountRefresh,
|
|
10000,
|
|
);
|
|
|
|
setIsPolling(true);
|
|
}, [machineStatus, poll, stopPolling, getPollInterval]);
|
|
|
|
return {
|
|
startPolling,
|
|
stopPolling,
|
|
isPolling,
|
|
};
|
|
}
|