From 0910dc44cfd51dc4895d8d68767020e0d4a4c858 Mon Sep 17 00:00:00 2001 From: csawatzky Date: Thu, 14 May 2026 13:32:53 -0600 Subject: [PATCH 1/2] changed up the scan dispaly to show expanders properly rather than just as another sensor with no expansion line, and added the lines to the port info in the component cards --- src/component/ComponentCard.tsx | 13 +++- .../autoDetect/DeviceScannedComponents.tsx | 67 +++++++++++++------ src/device/autoDetect/I2CExpander.tsx | 27 ++++++++ src/device/autoDetect/ScannedI2C.tsx | 1 + 4 files changed, 86 insertions(+), 22 deletions(-) create mode 100644 src/device/autoDetect/I2CExpander.tsx 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 2be89a9..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,17 +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]) @@ -256,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 ? From 74025290f177b6cbb4301df55efc11de814900eb Mon Sep 17 00:00:00 2001 From: csawatzky Date: Wed, 20 May 2026 10:38:46 -0600 Subject: [PATCH 2/2] created a new hook for handling fullscreen rather than using react-full-screen, hopefully that will solve the issues some users are seeing where it goes into full screnn and then immediately exits --- package-lock.json | 22 ------ package.json | 1 - src/bin/BinVisualizerV2.tsx | 8 +-- src/hooks/FullScreenHandle.tsx | 121 +++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 src/hooks/FullScreenHandle.tsx diff --git a/package-lock.json b/package-lock.json index a8d3068..58133fb 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", @@ -11516,21 +11509,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 90ac71b..a7afb36 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,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 f6f9c1d..1f7c26d 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/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