diff --git a/packages/client/src/components/App.tsx b/packages/client/src/components/App.tsx index 3d3655217e005805a23ec384f57743051ba0daba..238691c8f94d30113c4ce0cb01fc9216e7a0f866 100644 --- a/packages/client/src/components/App.tsx +++ b/packages/client/src/components/App.tsx @@ -24,6 +24,7 @@ import { Chat, ChatContext } from "./Chat/utils"; import { EventInfoModal } from "./EventInfo/EventInfo"; import AudioContext from "@/contexts/AudioContext"; import PanelContext from "@/contexts/PanelContext"; +import { CaptureContext } from "@/contexts/CaptureContext"; console.log("Client init with version " + __COMMIT_HASH__); @@ -82,7 +83,9 @@ const App = () => { - + + + diff --git a/packages/client/src/components/CanvasWrapper.tsx b/packages/client/src/components/CanvasWrapper.tsx index 8e50307e3afc86d8ae81da95ea699b3764bf23a1..56d982c3d3e6522f17c42badd26b770bdcb9c710 100644 --- a/packages/client/src/components/CanvasWrapper.tsx +++ b/packages/client/src/components/CanvasWrapper.tsx @@ -22,6 +22,7 @@ import { GridOverlay } from "./Overlay/GridOverlay"; import { PaletteLib } from "../lib/palette"; import { getRenderer } from "../lib/utils"; import { usePanel } from "@/contexts/PanelContext"; +import { useCaptureContext } from "@/contexts/CaptureContext"; export const CanvasWrapper = () => { const hasMod = useHasRole("MOD"); @@ -121,6 +122,12 @@ const CanvasInner = () => { enable: templateEnable, } = useTemplateContext(); const PanZoom = useContext(RendererContext); + const { + captureCanvas, + captureView, + captureRegion, + captureTemplateArea, + } = useCaptureContext(); /** * Is the canvas coordinate within the bounds of the canvas? @@ -237,17 +244,35 @@ const CanvasInner = () => { console.log("[CanvasWrapper] received canvasReady"); }); - // setup keybind listeners + return () => { + canvas.current?.destroy(); + }; + }, []); + + useEffect(() => { KeybindManager.on("PIXEL_WHOIS", handlePixelWhois); KeybindManager.on("PICK_COLOR", handlePickPixel); + KeybindManager.on("CAPTURE_CANVAS", captureCanvas); + KeybindManager.on("CAPTURE_VIEW", captureView); + KeybindManager.on("CAPTURE_REGION", captureRegion); + KeybindManager.on("CAPTURE_TEMPLATE", captureTemplateArea); return () => { - canvas.current?.destroy(); - KeybindManager.off("PIXEL_WHOIS", handlePixelWhois); KeybindManager.off("PICK_COLOR", handlePickPixel); + KeybindManager.off("CAPTURE_CANVAS", captureCanvas); + KeybindManager.off("CAPTURE_VIEW", captureView); + KeybindManager.off("CAPTURE_REGION", captureRegion); + KeybindManager.off("CAPTURE_TEMPLATE", captureTemplateArea); }; - }, []); + }, [ + handlePixelWhois, + handlePickPixel, + captureCanvas, + captureView, + captureRegion, + captureTemplateArea, + ]); useEffect(() => { canvas.current?.setPanZoom(PanZoom); diff --git a/packages/client/src/components/Header/HeaderRight.tsx b/packages/client/src/components/Header/HeaderRight.tsx index 7782cc260f4d0f040020a3fad846edd74dbf2200..4c59a7e64804aba3225134b9e871340821ba20d1 100644 --- a/packages/client/src/components/Header/HeaderRight.tsx +++ b/packages/client/src/components/Header/HeaderRight.tsx @@ -3,12 +3,24 @@ import { useAppContext } from "../../contexts/AppContext"; import { User } from "./User"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { ThemeSwitcher } from "./ThemeSwitcher"; -import { faCog, faGear, faHammer } from "@fortawesome/free-solid-svg-icons"; -import React, { lazy } from "react"; +import { + faBorderAll, + faCamera, + faChevronDown, + faCropSimple, + faEye, + faCog, + faGear, + faHammer, + faDrawPolygon, +} from "@fortawesome/free-solid-svg-icons"; +import React, { lazy, useEffect, useRef, useState } from "react"; import { useHasRole } from "../../hooks/useHasRole"; import { useModerator } from "../../Moderator/Moderator"; import { Button } from "../core/Button"; import { usePanel } from "@/contexts/PanelContext"; +import { useCaptureContext } from "@/contexts/CaptureContext"; +import { AnimatePresence, motion } from "framer-motion"; const OpenChatButton = lazy(() => import("../Chat/OpenChatButton")); @@ -21,13 +33,134 @@ const DynamicChat = () => { export const HeaderRight = () => { const { dispatch } = useModerator(); const Panel = usePanel(); + const { + captureCanvas, + captureView, + captureRegion, + captureTemplateArea, + hasTemplateCapture, + } = useCaptureContext(); const hasAdmin = useHasRole("ADMIN"); const hasMod = useHasRole("MOD"); + const captureMenuRef = useRef(null); + const [captureMenuOpen, setCaptureMenuOpen] = useState(false); + + useEffect(() => { + if (!captureMenuOpen) return; + + const handlePointerDown = (event: PointerEvent) => { + if ( + event.target instanceof Node && + captureMenuRef.current?.contains(event.target) + ) { + return; + } + + setCaptureMenuOpen(false); + }; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") setCaptureMenuOpen(false); + }; + + window.addEventListener("pointerdown", handlePointerDown); + window.addEventListener("keydown", handleKeyDown); + + return () => { + window.removeEventListener("pointerdown", handlePointerDown); + window.removeEventListener("keydown", handleKeyDown); + }; + }, [captureMenuOpen]); + + const handleCaptureCanvas = () => { + setCaptureMenuOpen(false); + captureCanvas(); + }; + + const handleCaptureView = () => { + setCaptureMenuOpen(false); + captureView(); + }; + + const handleCaptureRegion = () => { + setCaptureMenuOpen(false); + captureRegion(); + }; + + const handleCaptureTemplateArea = () => { + setCaptureMenuOpen(false); + captureTemplateArea(); + }; return (
+
+ + + {captureMenuOpen && ( + + + + + {hasTemplateCapture && ( + + )} + + )} + +