diff --git a/applications/virtual-fly-brain/frontend/src/components/Layout.jsx b/applications/virtual-fly-brain/frontend/src/components/Layout.jsx index a2413cb7..5785d70d 100644 --- a/applications/virtual-fly-brain/frontend/src/components/Layout.jsx +++ b/applications/virtual-fly-brain/frontend/src/components/Layout.jsx @@ -6,6 +6,7 @@ import ErrorDialog from "./ErrorDialog"; import QueryBuilder from "./queryBuilder"; import MediaQuery from 'react-responsive'; import VFBUploader from "./VFBUploader/VFBUploader"; +import VFBSnapshot from "./VFBSnapshot/VFBSnapshot"; import { useDispatch, useSelector, useStore } from 'react-redux'; import { widgets } from "./layout/widgets"; import VFBDownloadContents from "./VFBDownloadContents/VFBDownloadContents"; @@ -251,20 +252,22 @@ const MainLayout = ({ bottomNav, setBottomNav }) => { {desktopScreen ? ( <> {tabContent} - {bottomNav === 0 && < VFBUploader open={true} setBottomNav={setBottomNav} />} - {bottomNav === 1 && } - {bottomNav === 2 && } - {bottomNav === 5 && } + {bottomNav === 0 && < VFBSnapshot open={true} setBottomNav={setBottomNav} />} + {bottomNav === 1 && < VFBUploader open={true} setBottomNav={setBottomNav} />} + {bottomNav === 2 && } + {bottomNav === 3 && } + {bottomNav === 6 && } ) : ( <> { - bottomNav != 2 && tabContent + bottomNav != 3 && tabContent } - {bottomNav === 0 && } - {bottomNav === 1 && } - {bottomNav === 2 && } - {bottomNav === 5 && } + {bottomNav === 0 && } + {bottomNav === 1 && } + {bottomNav === 2 && } + {bottomNav === 3 && } + {bottomNav === 6 && } )} diff --git a/applications/virtual-fly-brain/frontend/src/components/VFBSnapshot/VFBSnapshot.jsx b/applications/virtual-fly-brain/frontend/src/components/VFBSnapshot/VFBSnapshot.jsx new file mode 100644 index 00000000..3ed7f565 --- /dev/null +++ b/applications/virtual-fly-brain/frontend/src/components/VFBSnapshot/VFBSnapshot.jsx @@ -0,0 +1,96 @@ +import React, { useEffect } from "react"; +import * as htmlToImage from "html-to-image"; + +const VFBSnapshot = ({ open, setBottomNav }) => { + useEffect(() => { + if (!open) return; + + // screenshot flash effect + const flash = document.createElement("div"); + flash.style.position = "fixed"; + flash.style.left = 0; + flash.style.top = 0; + flash.style.width = "100vw"; + flash.style.height = "100vh"; + flash.style.background = "white"; + flash.style.opacity = "0.7"; + flash.style.zIndex = "2147483647"; + flash.style.pointerEvents = "none"; + flash.style.transition = "opacity 100ms"; + document.body.appendChild(flash); + + setTimeout(() => { + flash.style.opacity = "0"; + setTimeout(() => { document.body.removeChild(flash); }, 120); + }, 100); + + // Hide scrollbars and remove container max-height/overflow for all scrollable area + const selectors = [ + ".MuiBox-root", + ".MuiPaper-root", + ".scrollable", + ".vfb-scrollable", + ".subheader-content", + ".VFBMainPanel", + ]; + const containers = Array.from(document.querySelectorAll(selectors.join(','))); + + // Save previous styles so we can restore exactly + const previousStyles = containers.map(el => ({ + el, + overflow: el.style.overflow, + overflowX: el.style.overflowX, + overflowY: el.style.overflowY, + maxHeight: el.style.maxHeight, + maxWidth: el.style.maxWidth, + })); + + // Apply snapshot styles (no overflow, no artificial maxHeight/Width) + containers.forEach(el => { + el.style.overflow = "visible"; + el.style.overflowX = "visible"; + el.style.overflowY = "visible"; + el.style.maxHeight = "unset"; + el.style.maxWidth = "unset"; + }); + + (async () => { + try { + const el = document.getElementById('root') || document.body; + const dataUrl = await htmlToImage.toPng(el, { cacheBust: true }); + const link = document.createElement("a"); + link.download = "vfb-snapshot.png"; + link.href = dataUrl; + link.click(); + } catch (err) { + console.error("[VFBSnapshot] Error capturing snapshot:", err); + } + + // Restore overflow/maxHeight styles + previousStyles.forEach(({ el, overflow, overflowX, overflowY, maxHeight, maxWidth }) => { + el.style.overflow = overflow; + el.style.overflowX = overflowX; + el.style.overflowY = overflowY; + el.style.maxHeight = maxHeight; + el.style.maxWidth = maxWidth; + }); + + setBottomNav(undefined); + })(); + + return () => { + previousStyles.forEach(({ el, overflow, overflowX, overflowY, maxHeight, maxWidth }) => { + el.style.overflow = overflow; + el.style.overflowX = overflowX; + el.style.overflowY = overflowY; + el.style.maxHeight = maxHeight; + el.style.maxWidth = maxWidth; + }); + }; + + }, [open, setBottomNav]); + + return null; +}; + +export default VFBSnapshot; \ No newline at end of file diff --git a/applications/virtual-fly-brain/frontend/src/icons/index.jsx b/applications/virtual-fly-brain/frontend/src/icons/index.jsx index 3c809134..f3492c57 100644 --- a/applications/virtual-fly-brain/frontend/src/icons/index.jsx +++ b/applications/virtual-fly-brain/frontend/src/icons/index.jsx @@ -88,6 +88,24 @@ export const Search = (props) => { ) }; +export const Screenshot = (props) => { + return ( + + + + + + + + + + + + + + ) +}; + export const Upload = (props) => { return ( diff --git a/applications/virtual-fly-brain/frontend/src/shared/subHeader/index.jsx b/applications/virtual-fly-brain/frontend/src/shared/subHeader/index.jsx index 63506f3f..6780c02a 100644 --- a/applications/virtual-fly-brain/frontend/src/shared/subHeader/index.jsx +++ b/applications/virtual-fly-brain/frontend/src/shared/subHeader/index.jsx @@ -13,8 +13,9 @@ import { Layers, Query, Search, - Upload, + Upload } from "../../icons"; +import Screenshot from '@mui/icons-material/CameraAlt'; import vars from "../../theme/variables"; import MediaQuery from "react-responsive"; import { useDispatch, useSelector } from "react-redux"; @@ -25,31 +26,36 @@ import { resetLoadingState } from "../../reducers/actions/instances"; const navArr = [ { id: 0, + icon: Screenshot, + name: "Screenshot", + }, + { + id: 1, icon: Upload, name: "Upload", }, { - id: 1, + id: 2, icon: Download, name: "Download", }, { - id: 2, + id: 3, icon: Query, name: "Query", }, { - id: 3, + id: 4, icon: Layers, name: "Layer", }, { - id: 4, + id: 5, icon: ClearAll, name: "Clear all", }, { - id: 5, + id: 6, icon: History, name: "Recent", },