diff --git a/package-lock.json b/package-lock.json index 263077d..b2b0e22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,6 @@ "react-dom": "^18.3.1", "react-emoji-render": "^2.0.1", "react-error-boundary": "^5.0.0", - "react-full-screen": "^1.1.1", "react-horizontal-scrolling-menu": "^7.1.1", "react-infinite-scroller": "^1.2.6", "react-joyride": "^2.9.3", @@ -8617,12 +8616,6 @@ "node": ">=10" } }, - "node_modules/fscreen": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz", - "integrity": "sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==", - "license": "MIT" - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -11517,21 +11510,6 @@ "is-lite": "^0.8.2" } }, - "node_modules/react-full-screen": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-full-screen/-/react-full-screen-1.1.1.tgz", - "integrity": "sha512-xoEgkoTiN0dw9cjYYGViiMCBYbkS97BYb4bHPhQVWXj1UnOs8PZ1rPzpX+2HMhuvQV1jA5AF9GaRbO3fA5aZtg==", - "license": "MIT", - "dependencies": { - "fscreen": "^1.0.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">= 16.8.0" - } - }, "node_modules/react-horizontal-scrolling-menu": { "version": "7.1.8", "resolved": "https://registry.npmjs.org/react-horizontal-scrolling-menu/-/react-horizontal-scrolling-menu-7.1.8.tgz", diff --git a/package.json b/package.json index 265b238..ceeb1af 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "react-dom": "^18.3.1", "react-emoji-render": "^2.0.1", "react-error-boundary": "^5.0.0", - "react-full-screen": "^1.1.1", "react-horizontal-scrolling-menu": "^7.1.1", "react-infinite-scroller": "^1.2.6", "react-joyride": "^2.9.3", diff --git a/src/bin/BinVisualizerV2.tsx b/src/bin/BinVisualizerV2.tsx index 437f09d..c7597f1 100644 --- a/src/bin/BinVisualizerV2.tsx +++ b/src/bin/BinVisualizerV2.tsx @@ -36,7 +36,6 @@ import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { useGlobalState, useSnackbar } from "providers"; import React, { useCallback, useEffect, useState } from "react"; -import { FullScreen, useFullScreenHandle } from "react-full-screen"; import moment, { Moment } from "moment"; import ResponsiveDialog from "common/ResponsiveDialog"; import { getThemeType } from "theme"; @@ -72,6 +71,7 @@ import ButtonGroup from "common/ButtonGroup"; import ModeChangeDialog from "./conditioning/modeChangeDialog"; import CustomGrainSelector from "grain/CustomGrainSelector"; import BinControllerDisplay from "./BinControllerDisplay"; +import { useFullScreen } from "hooks/FullScreenHandle"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -217,7 +217,7 @@ export default function BinVisualizer(props: Props) { const isMobile = useMobile(); const classes = useStyles(); const theme = useTheme(); - const fullScreenHandler = useFullScreenHandle(); + const {fullScreenHandler, FullScreenWrapper} = useFullScreen() const viewport = useViewport(); const { openSnack } = useSnackbar(); const [fillPercentage, setFillPercentage] = useState(0); @@ -1344,7 +1344,7 @@ export default function BinVisualizer(props: Props) { return ( - + - + ); }; diff --git a/src/component/ComponentCard.tsx b/src/component/ComponentCard.tsx index 6d430d5..8355abe 100644 --- a/src/component/ComponentCard.tsx +++ b/src/component/ComponentCard.tsx @@ -190,7 +190,18 @@ export default function ComponentCard(props: Props) { cableID = "Cable: " + (component.settings.addressType - 8); } return port + " " + cableID; - } else { + } else if (component.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) { + let description = "" + let type = getFriendlyAddressTypeName(component.settings.addressType); + description = type + if(component.settings.expansionLine){ + description = description + ": Exp Line " + component.settings.expansionLine + } + if(component.settings.muxLine){ + description = description + " ,Mux Line " + component.settings.muxLine + } + return description + }else{ return getFriendlyAddressTypeName(component.settings.addressType); } }; diff --git a/src/device/autoDetect/DeviceScannedComponents.tsx b/src/device/autoDetect/DeviceScannedComponents.tsx index dda2bf4..95c3759 100644 --- a/src/device/autoDetect/DeviceScannedComponents.tsx +++ b/src/device/autoDetect/DeviceScannedComponents.tsx @@ -12,6 +12,7 @@ import ResponsiveDialog from "common/ResponsiveDialog"; import { useComponentAPI, useDeviceAPI, useSnackbar } from "hooks"; import { ConfigurablePin } from "pbHelpers/AddressTypes"; import ScannedOneWirePort from "./OneWire/ScannedOneWirePort"; +import I2CExpander from "./I2CExpander"; interface Props { scannedComponents: pond.ComponentAddressMap @@ -27,6 +28,7 @@ interface CompStep { } let i2cBlacklistAddresses: number[] = [0x70] +let i2cExpanderAddresses: number[] = [0x71] export default function DeviceScannedComponents(props: Props){ const {scannedComponents, device, availablePositions, availableOffsets, refreshCallback} = props @@ -35,6 +37,7 @@ export default function DeviceScannedComponents(props: Props){ const [scannedI2C, setScannedI2C] = useState() //the unmodified scan of addresses const [scannedOneWire, setScannedOneWire] = useState([]) const [validI2CCompAddresses, setValidI2CComponentAddresses] = useState([]) //the filtered array of address data + const [expanderAddresses, setExpanderAddresses] = useState([]) const [components, setComponents] = useState([]) const [steps, setSteps] = useState([]); const { error, success } = useSnackbar(); @@ -69,18 +72,23 @@ export default function DeviceScannedComponents(props: Props){ useEffect(()=>{ let valid: quack.AddressData[] = [] + let expanders: quack.AddressData[] = [] //filter the address data let addr = scannedI2C?.settings?.foundAddresses console.log(addr) if(addr){ addr.forEach(addrData => { if(!i2cBlacklistAddresses.includes(addrData.address)){ - if(!(addrData.address === 0x71 && addrData.expansionLine === undefined)){ - valid.push(addrData) - } + if(i2cExpanderAddresses.includes(addrData.address) && addrData.expansionLine === 0){ + expanders.push(addrData) + }else{ + valid.push(addrData) + } } }) } + console.log(valid) + setExpanderAddresses(expanders) setValidI2CComponentAddresses(valid) },[scannedI2C]) @@ -257,33 +265,49 @@ export default function DeviceScannedComponents(props: Props){ {scannedI2C !== undefined && - - I2C Sensors + + I2C Scan - {validI2CCompAddresses.length > 0 ? validI2CCompAddresses.map((addr, index) => { - return ( - ()}/> - ) - }) : - - - No Sensors Found - + {expanderAddresses.length > 0 && + + Detected Expanders + {expanderAddresses.map((expAddr, index) => { + return ( + + ) + })} + + } + + {validI2CCompAddresses.length > 0 ? + + Detected Sensors + {validI2CCompAddresses.map((addr, index) => { + return ( + ()}/> + ) + })} + + : + + + No Sensors Found + + + } } - - } {scannedOneWire.length > 0 && - Pin Port Sensors + Pin Port Scan {scannedOneWire.map((scan,i) => { diff --git a/src/device/autoDetect/I2CExpander.tsx b/src/device/autoDetect/I2CExpander.tsx new file mode 100644 index 0000000..f2a8183 --- /dev/null +++ b/src/device/autoDetect/I2CExpander.tsx @@ -0,0 +1,27 @@ +import { Box, Typography } from "@mui/material" +import { quack } from "protobuf-ts/quack" + +interface Props { + address: quack.AddressData +} + + + +export default function I2CExpander(props: Props) { + const {address} = props + + const expanderType = () => { + switch(address.address){ + case 0x71: + return "Grain Cable Expander" + default: + return "Unknown Expander" + } + } + + return ( + + {expanderType()} {address.muxLine ? "Mux Line: " + address.muxLine : ""} + + ) +} \ No newline at end of file diff --git a/src/device/autoDetect/ScannedI2C.tsx b/src/device/autoDetect/ScannedI2C.tsx index 4719f66..3f1b461 100644 --- a/src/device/autoDetect/ScannedI2C.tsx +++ b/src/device/autoDetect/ScannedI2C.tsx @@ -108,6 +108,7 @@ export default function ScannedI2C(props: Props){ {sensorNum && Sensor {sensorNum}} {addressData.expansionLine !== 0 && "Expansion Line: " + addressData.expansionLine} + {/* may also want to specify the mux line, not sure if the sensors will have a mux line though */} {types.length > 0 ? diff --git a/src/hooks/FullScreenHandle.tsx b/src/hooks/FullScreenHandle.tsx new file mode 100644 index 0000000..9e4323b --- /dev/null +++ b/src/hooks/FullScreenHandle.tsx @@ -0,0 +1,121 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +/** + * Drop-in replacement for react-full-screen's useFullScreenHandle + FullScreen component. + * + * Motivation: react-full-screen 1.1.1 doesn't debounce fullscreenchange events, so on + * Windows systems with DPI scaling > 100%, the resize triggered during fullscreen entry + * fires a fullscreenchange event that the library misinterprets as an exit, immediately + * popping back out of fullscreen. + * + * This hook bypasses the library entirely and calls the native Fullscreen API directly, + * ignoring fullscreenchange events fired within DEBOUNCE_MS of entry to absorb the + * DPI-scaling resize blip. + * + * Usage — replace in BinVisualizerV2.tsx: + * + * // Remove: + * import { FullScreen, useFullScreenHandle } from "react-full-screen"; + * const fullScreenHandler = useFullScreenHandle(); + * ... + * + * // Add: + * import { useFullScreen } from "hooks/useFullScreen"; + * const { fullScreenHandler, FullScreenWrapper } = useFullScreen(); + * ... + * + * // Everything else (fullScreenHandler.active, .enter(), .exit()) stays the same. + */ + +const DEBOUNCE_MS = 500; + +export interface FullScreenHandle { + active: boolean; + enter: () => void; + exit: () => void; +} + +export function useFullScreen(): { + fullScreenHandler: FullScreenHandle; + FullScreenWrapper: React.FC<{ children: React.ReactNode }>; +} { + const [active, setActive] = useState(false); + const containerRef = useRef(null); + // Timestamp of the last enter() call — used to debounce spurious exit events + const enterTimeRef = useRef(0); + + const enter = useCallback(() => { + const el = containerRef.current; + if (!el) return; + enterTimeRef.current = Date.now(); + if (el.requestFullscreen) { + el.requestFullscreen().catch(() => { + // Some browsers (e.g. iOS Safari) reject the promise — silently ignore + }); + } else if ((el as any).webkitRequestFullscreen) { + (el as any).webkitRequestFullscreen(); + } else if ((el as any).mozRequestFullScreen) { + (el as any).mozRequestFullScreen(); + } else if ((el as any).msRequestFullscreen) { + (el as any).msRequestFullscreen(); + } + }, []); + + const exit = useCallback(() => { + if (document.exitFullscreen) { + document.exitFullscreen().catch(() => {}); + } else if ((document as any).webkitExitFullscreen) { + (document as any).webkitExitFullscreen(); + } else if ((document as any).mozCancelFullScreen) { + (document as any).mozCancelFullScreen(); + } else if ((document as any).msExitFullscreen) { + (document as any).msExitFullscreen(); + } + }, []); + + useEffect(() => { + const handleChange = () => { + const fullscreenEl = + document.fullscreenElement || + (document as any).webkitFullscreenElement || + (document as any).mozFullScreenElement || + (document as any).msFullscreenElement; + + const isNowFullscreen = fullscreenEl === containerRef.current; + + // If we just called enter() and this event fires within DEBOUNCE_MS, + // and it looks like an exit, ignore it — it's the DPI-scaling resize blip + if (!isNowFullscreen && Date.now() - enterTimeRef.current < DEBOUNCE_MS) { + return; + } + + setActive(isNowFullscreen); + }; + + document.addEventListener("fullscreenchange", handleChange); + document.addEventListener("webkitfullscreenchange", handleChange); + document.addEventListener("mozfullscreenchange", handleChange); + document.addEventListener("MSFullscreenChange", handleChange); + + return () => { + document.removeEventListener("fullscreenchange", handleChange); + document.removeEventListener("webkitfullscreenchange", handleChange); + document.removeEventListener("mozfullscreenchange", handleChange); + document.removeEventListener("MSFullscreenChange", handleChange); + }; + }, []); + + const FullScreenWrapper: React.FC<{ children: React.ReactNode }> = useCallback( + ({ children }) => ( +
+ {children} +
+ ), + [] + ); + + return { + fullScreenHandler: { active, enter, exit }, + FullScreenWrapper, + }; +} \ No newline at end of file