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"
+ }
+}