diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 44c5c25..93ebb29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,7 @@ "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.5.0", "@types/video.js": "^7.3.58", + "html2canvas": "^1.4.1", "lodash": "^4.17.21", "react": "^18.3.1", "react-chrome-dino": "^0.1.3", @@ -1953,6 +1954,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2178,6 +2188,15 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2830,6 +2849,19 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4233,6 +4265,15 @@ "node": ">=14.0.0" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -4388,6 +4429,15 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/video.js": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/video.js/-/video.js-8.21.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1b5cbca..2f9b28a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "@stripe/react-stripe-js": "^3.1.1", "@stripe/stripe-js": "^5.5.0", "@types/video.js": "^7.3.58", + "html2canvas": "^1.4.1", "lodash": "^4.17.21", "react": "^18.3.1", "react-chrome-dino": "^0.1.3", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4c1b16c..4f90c7d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -13,6 +13,7 @@ import ResultsPage from "./pages/ResultsPage"; import { SidebarProvider } from "./context/SidebarContext"; import { QuickSettingsProvider } from "./context/QuickSettingsContext"; import StreamDashboardPage from "./pages/StreamDashboardPage"; +import { Brightness } from "./context/BrightnessContext"; function App() { const [isLoggedIn, setIsLoggedIn] = useState(false); @@ -34,6 +35,7 @@ function App() { }, []); return ( + @@ -73,6 +75,7 @@ function App() { + ); } diff --git a/frontend/src/components/Settings/QuickSettings.tsx b/frontend/src/components/Settings/QuickSettings.tsx index d7c6042..a2c4bec 100644 --- a/frontend/src/components/Settings/QuickSettings.tsx +++ b/frontend/src/components/Settings/QuickSettings.tsx @@ -2,6 +2,8 @@ import React from "react"; import ThemeSetting from "./ThemeSetting"; import { useTheme } from "../../context/ThemeContext"; import { useQuickSettings } from "../../context/QuickSettingsContext"; +import Screenshot from "../functionality/Screenshot" +import BrightnessControl from "../functionality/BrightnessControl"; const QuickSettings: React.FC = () => { const { theme } = useTheme(); @@ -19,6 +21,9 @@ const QuickSettings: React.FC = () => {
+ + + ); }; diff --git a/frontend/src/components/functionality/BrightnessControl.tsx b/frontend/src/components/functionality/BrightnessControl.tsx new file mode 100644 index 0000000..4166eca --- /dev/null +++ b/frontend/src/components/functionality/BrightnessControl.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useBrightness } from "../../context/BrightnessContext"; + +const BrightnessControl: React.FC = () => { + const { brightness, setBrightness } = useBrightness(); + + const handleBrightnessChange = (event: React.ChangeEvent) => { + {/* Set brightness based on the value. Calls BrightnessContext too */} + setBrightness(Number(event.target.value)); + }; + + return ( +
+

Brightness Control

+ {/* Changes based on the range of input */} + + +

Brightness: {brightness}%

+
+ ); +}; + +export default BrightnessControl; diff --git a/frontend/src/components/functionality/Screenshot.tsx b/frontend/src/components/functionality/Screenshot.tsx new file mode 100644 index 0000000..51dd654 --- /dev/null +++ b/frontend/src/components/functionality/Screenshot.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import html2canvas from "html2canvas"; + +const Screenshot: React.FC = () => { + const captureScreenshot = async () => { + const targetElement = document.getElementById("root"); // Capture entire HTML document + + if (!targetElement) { + console.error("Target element not found"); + return; + } + + try { + // Ensure everything is visible before capturing + document.body.style.overflow = "visible"; + document.documentElement.style.overflow = "visible"; + + const canvas = await html2canvas(targetElement, { + useCORS: true, // Enables external image capture (CORS-safe) + scale: 2, // Higher resolution screenshot + backgroundColor: "#fff", // Ensures non-transparent background + windowWidth: document.documentElement.scrollWidth, // Capture full width + windowHeight: document.documentElement.scrollHeight, // Capture full height + }); + + // Restore overflow settings + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; + + const image = canvas.toDataURL("image/png"); + const link = document.createElement("a"); + link.href = image; + link.download = `screenshot-${Date.now()}.png`; + link.click(); + } catch (error) { + console.error("Screenshot capture failed:", error); + } + }; + + return ( + + ); +}; + +export default Screenshot; diff --git a/frontend/src/context/BrightnessContext.tsx b/frontend/src/context/BrightnessContext.tsx new file mode 100644 index 0000000..44e19cd --- /dev/null +++ b/frontend/src/context/BrightnessContext.tsx @@ -0,0 +1,31 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; + +interface BrightnessContextType { + brightness: number; + setBrightness: (value: number) => void; +} + +const BrightnessContext = createContext(undefined); + +export const Brightness: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [brightness, setBrightness] = useState(100); + + useEffect(() => { + // Apply brightness to the entire page + document.body.style.filter = `brightness(${brightness}%)`; + }, [brightness]); + + return ( + + {children} + + ); +}; + +export const useBrightness = (): BrightnessContextType => { + const context = useContext(BrightnessContext); + if (!context) { + throw new Error("useBrightness must be used within a BrightnessProvider"); + } + return context; +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 3de74d7..1c3ce53 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,13 +1,10 @@ -import { StrictMode } from "react"; import { ThemeProvider } from "./context/ThemeContext"; import { createRoot } from "react-dom/client"; import "./assets/styles/index.css"; import App from "./App.tsx"; createRoot(document.getElementById("root")!).render( - // - // , ); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9b19e49 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,86 @@ +{ + "name": "cs3305-team11", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "html2canvas": "^1.4.1", + "react": "^19.0.0", + "use-react-screenshot": "^4.0.0" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/use-react-screenshot": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/use-react-screenshot/-/use-react-screenshot-4.0.0.tgz", + "integrity": "sha512-4UZIORp7iCklfNOS/dPJab9SPeGdS0nFyIi3qA1rfMyYf/em/KfodYhrOlSHAHWvfdeCrS67Jjk6H4M4oLYSWg==", + "license": "MIT", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "html2canvas": "^1.3.3", + "react": "^18.2.0" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..26a7974 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "html2canvas": "^1.4.1", + "react": "^19.0.0", + "use-react-screenshot": "^4.0.0" + } +}