diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 0000000..13040da --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": true, + "singleQuote": false, + "useTabs": false, + "printWidth": 100, + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e1d1c23..96a6f1f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,7 +11,9 @@ "@tailwindcss/vite": "^4.1.11", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.7.0", + "sonner": "^2.0.6", "tailwindcss": "^4.1.11" }, "devDependencies": { @@ -20,10 +22,12 @@ "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.7.0", "@vitejs/plugin-react-swc": "^3.11.0", + "daisyui": "^5.0.46", "eslint": "^9.31.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", + "prettier-plugin-tailwindcss": "^0.6.14", "vite": "^7.0.5" } }, @@ -2093,6 +2097,16 @@ "dev": true, "license": "MIT" }, + "node_modules/daisyui": { + "version": "5.0.46", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.46.tgz", + "integrity": "sha512-vMDZK1tI/bOb2Mc3Mk5WpquBG3ZqBz1YKZ0xDlvpOvey60dOS4/5Qhdowq1HndbQl7PgDLDYysxAjjUjwR7/eQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/saadeghi/daisyui?sponsor=1" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -3204,6 +3218,110 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3235,6 +3353,15 @@ "react": "^19.1.0" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -3377,6 +3504,16 @@ "node": ">=8" } }, + "node_modules/sonner": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.6.tgz", + "integrity": "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3e0425e..9826443 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,9 @@ "@tailwindcss/vite": "^4.1.11", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-icons": "^5.5.0", "react-router-dom": "^7.7.0", + "sonner": "^2.0.6", "tailwindcss": "^4.1.11" }, "devDependencies": { @@ -22,10 +24,12 @@ "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.7.0", "@vitejs/plugin-react-swc": "^3.11.0", + "daisyui": "^5.0.46", "eslint": "^9.31.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", + "prettier-plugin-tailwindcss": "^0.6.14", "vite": "^7.0.5" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4b5b5ae..fb648e2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,28 +1,41 @@ -import { useState, useEffect } from "react"; -import { HashRouter as Router, Routes, Route, Navigate } from "react-router-dom"; -import TopNav from "./components/topnav"; -import SideNav from "./components/sidenav"; +import { useEffect, useState } from "react"; +import { Navigate, Route, HashRouter as Router, Routes } from "react-router-dom"; +import About from "./components/about"; +import Container from "./components/container"; import Results from "./components/results"; import Settings from "./components/settings"; +import TabNavigation from "./components/tabnavigation"; -function App() { - const [isSideNavOpen, setIsSideNavOpen] = useState(false); - const [validConfig, setValidConfig] = useState(null); // null = loading +const App = () => { + const [validConfig, setValidConfig] = useState(null); useEffect(() => { - chrome.storage.local.get("valid_config", (result) => { - if (chrome.runtime.lastError) { - console.error("Error reading valid_config:", chrome.runtime.lastError); - setValidConfig(false); // fallback - } else { - setValidConfig(result.valid_config === true); - } - }); + // Fix: Access chrome API properly for browser extensions + if (typeof chrome !== "undefined" && chrome.storage) { + chrome.storage.local.get("valid_config", (result) => { + if (chrome.runtime.lastError) { + console.error("Error reading valid_config:", chrome.runtime.lastError); + setValidConfig(false); + } else { + setValidConfig(result.valid_config === true); + } + }); + } else { + // Fallback for development/testing + setValidConfig(false); + } }, []); + const handleConfigSaved = () => { + setValidConfig(true); + // Navigate to main tab after config is saved + window.location.hash = "#/results"; + }; + if (validConfig === null) { return ( -
+
+ Loading...
); @@ -30,20 +43,16 @@ function App() { return ( -
-
- setIsSideNavOpen(true)} /> -
- -
+
+ + +
{!validConfig ? ( <> setValidConfig(true)} /> - } + element={} /> } /> @@ -52,21 +61,14 @@ function App() { } /> } /> } /> + } /> )} -
- -
- setIsSideNavOpen(false)} /> -
+
); -} +}; export default App; diff --git a/frontend/src/assets/fonts/InterVariable-Italic.woff2 b/frontend/src/assets/fonts/InterVariable-Italic.woff2 new file mode 100644 index 0000000..b3530f3 Binary files /dev/null and b/frontend/src/assets/fonts/InterVariable-Italic.woff2 differ diff --git a/frontend/src/assets/fonts/InterVariable.woff2 b/frontend/src/assets/fonts/InterVariable.woff2 new file mode 100644 index 0000000..5a8d3e7 Binary files /dev/null and b/frontend/src/assets/fonts/InterVariable.woff2 differ diff --git a/frontend/src/assets/fonts/font-face.css b/frontend/src/assets/fonts/font-face.css new file mode 100644 index 0000000..d12308e --- /dev/null +++ b/frontend/src/assets/fonts/font-face.css @@ -0,0 +1,15 @@ +@font-face { + font-family: Inter; + src: url("./InterVariable.woff2"); + font-style: normal; + font-weight: 300 900; + font-display: swap; +} + +@font-face { + font-family: Inter; + src: url("./InterVariable-Italic.woff2"); + font-style: italic; + font-weight: 300 900; + font-display: swap; +} diff --git a/frontend/src/components/about.jsx b/frontend/src/components/about.jsx new file mode 100644 index 0000000..331ac22 --- /dev/null +++ b/frontend/src/components/about.jsx @@ -0,0 +1,57 @@ +import { FaDiscord, FaTelegram } from "react-icons/fa"; +import { SiGitea } from "react-icons/si"; + +const AboutPage = () => { + const socialLinks = [ + { + name: "Discord", + icon: , + url: "https://discord.cdrm-project.com/", + description: "Join our Discord community", + color: "hover:text-indigo-400", + }, + { + name: "Telegram", + icon: , + url: "https://telegram.cdrm-project.com/", + description: "Follow us on Telegram", + color: "hover:text-sky-400", + }, + { + name: "Gitea", + icon: , + url: "https://cdm-project.com/tpd94/cdrm-project", + description: "Check out our code", + color: "hover:text-lime-400", + }, + ]; + + return ( +
+
+

Connect with us

+

Join our community and stay updated

+
+ +
+ {socialLinks.map((link) => ( + +
+
{link.icon}
+

{link.name}

+

{link.description}

+
+
+ ))} +
+
+ ); +}; + +export default AboutPage; diff --git a/frontend/src/components/container.jsx b/frontend/src/components/container.jsx new file mode 100644 index 0000000..d25cc18 --- /dev/null +++ b/frontend/src/components/container.jsx @@ -0,0 +1,9 @@ +const Container = ({ children, className = "", ...props }) => { + return ( +
+ {children} +
+ ); +}; + +export default Container; diff --git a/frontend/src/components/injectionmenu.jsx b/frontend/src/components/injectionmenu.jsx new file mode 100644 index 0000000..780ce93 --- /dev/null +++ b/frontend/src/components/injectionmenu.jsx @@ -0,0 +1,72 @@ +import { useEffect, useState } from "react"; + +const InjectionMenu = () => { + const [injectionType, setInjectionType] = useState("LICENSE"); + const [drmOverride, setDrmOverride] = useState("DISABLED"); + + useEffect(() => { + chrome.storage.local.get(["injection_type", "drm_override"], (result) => { + if (result.injection_type !== undefined) { + setInjectionType(result.injection_type); + } + if (result.drm_override !== undefined) { + setDrmOverride(result.drm_override); + } + }); + }, []); + + const handleInjectionTypeChange = (type) => { + chrome.storage.local.set({ injection_type: type }, () => { + if (chrome.runtime.lastError) { + console.error("Error updating injection_type:", chrome.runtime.lastError); + } else { + setInjectionType(type); + console.log(`Injection type updated to ${type}`); + } + }); + }; + + const handleDrmOverrideChange = (type) => { + chrome.storage.local.set({ drm_override: type }, () => { + if (chrome.runtime.lastError) { + console.error("Error updating drm_override:", chrome.runtime.lastError); + } else { + setDrmOverride(type); + console.log(`DRM Override updated to ${type}`); + } + }); + }; + + return ( + + ); +}; + +export default InjectionMenu; diff --git a/frontend/src/components/results.jsx b/frontend/src/components/results.jsx index 4b60d67..42080f6 100644 --- a/frontend/src/components/results.jsx +++ b/frontend/src/components/results.jsx @@ -1,6 +1,9 @@ import React, { useEffect, useState } from "react"; +import { IoCameraOutline, IoCopyOutline, IoSaveOutline } from "react-icons/io5"; +import { toast } from "sonner"; +import InjectionMenu from "./injectionmenu"; -function Results() { +const Results = () => { const [drmType, setDrmType] = useState(""); const [pssh, setPssh] = useState(""); const [licenseUrl, setLicenseUrl] = useState(""); @@ -118,6 +121,11 @@ function Results() { }); }; + const handleCopyToClipboard = (text, label) => { + navigator.clipboard.writeText(text); + toast.success(`Copied ${label} to clipboard`); + }; + // Check if current tab is YouTube const isYouTube = () => { return currentTabUrl.includes("youtube.com") || currentTabUrl.includes("youtu.be"); @@ -177,74 +185,137 @@ function Results() { }; return ( -
- -

DRM Type

- +
+ DRM Type + +
-

Manifest URL

- - -

PSSH

- - -

License URL

- - -

Keys

-
- {Array.isArray(keys) && keys.filter((k) => k.type !== "SIGNING").length > 0 ? ( - keys - .filter((k) => k.type !== "SIGNING") - .map((k) => `${k.key_id || k.keyId}:${k.key}`) - .join("\n") - ) : ( - [Not available] +
+ Manifest URL + + {!isYouTube() && manifestUrl && ( +

+ +

)} -
+ + +
+ PSSH + + {pssh && ( +

+ +

+ )} +
+ +
+ License URL + + {licenseUrl && ( +

+ +

+ )} +
+ +
+ Keys +