diff --git a/interface/src/App.tsx b/interface/src/App.tsx index 907dcb39..fb392125 100644 --- a/interface/src/App.tsx +++ b/interface/src/App.tsx @@ -1,31 +1,18 @@ -// App.tsx -import React, { useState } from 'react'; +import React from 'react'; import TabbedContainer from './components/TabbedContainer'; import Settings from './pages/Settings'; import logo from './assets/betterseqta-dark-full.png'; import logoDark from './assets/betterseqta-light-full.png'; import Shortcuts from './pages/Shortcuts'; import About from './pages/About'; - -import type { SettingsState } from './types/AppProps'; -import useSettingsState from './hooks/settingsState'; +import { SettingsContextProvider } from './SettingsContext'; const App: React.FC = () => { - const [settingsState, setSettingsState] = useState({ - notificationCollector: false, - lessonAlerts: false, - animatedBackground: false, - animatedBackgroundSpeed: "0", - customThemeColor: "#db6969", - betterSEQTAPlus: true - }); - - useSettingsState({ settingsState, setSettingsState }); const tabs = [ { title: 'Settings', - content: + content: }, { title: 'Shortcuts', @@ -38,13 +25,15 @@ const App: React.FC = () => { ]; return ( -
-
- - + +
+
+ + +
+
- -
+ ); }; diff --git a/interface/src/SettingsContext.tsx b/interface/src/SettingsContext.tsx new file mode 100644 index 00000000..302cf2aa --- /dev/null +++ b/interface/src/SettingsContext.tsx @@ -0,0 +1,39 @@ +// SettingsContext.tsx +import React, { createContext, useContext, useState, ReactNode } from 'react'; +import { SettingsState } from './types/AppProps'; +import useSettingsState from './hooks/settingsState'; + +// Create a context with an initial state +const SettingsContext = createContext<{ + settingsState: SettingsState; + setSettingsState: React.Dispatch>; +} | undefined>(undefined); + +export const SettingsContextProvider: React.FC<{ children: ReactNode }> = ({ children }) => { + const [settingsState, setSettingsState] = useState({ + notificationCollector: false, + lessonAlerts: false, + animatedBackground: false, + animatedBackgroundSpeed: "0", + customThemeColor: "#db6969", + betterSEQTAPlus: true, + shortcuts: [] + }); + + useSettingsState({ settingsState, setSettingsState }); + + return ( + + {children} + + ); +}; + +// eslint-disable-next-line +export const useSettingsContext = () => { + const context = useContext(SettingsContext); + if (!context) { + throw new Error('useSettingsContext must be used within a SettingsContextProvider'); + } + return context; +}; diff --git a/interface/src/components/ColorPicker.tsx b/interface/src/components/ColorPicker.tsx index 66380363..b9b6ee80 100644 --- a/interface/src/components/ColorPicker.tsx +++ b/interface/src/components/ColorPicker.tsx @@ -22,18 +22,20 @@ const Picker = ({ color, onChange }: ColorPickerProps) => { }, [showPicker]); return ( -
+ <> {showPicker && ( -
- +
+
+ +
)} -
+ ); }; diff --git a/interface/src/components/TabbedContainer.tsx b/interface/src/components/TabbedContainer.tsx index 9d55daf8..e29c9137 100644 --- a/interface/src/components/TabbedContainer.tsx +++ b/interface/src/components/TabbedContainer.tsx @@ -1,13 +1,15 @@ import React, { useState, useRef, useEffect } from 'react'; import { motion } from 'framer-motion'; import type { TabbedContainerProps } from '../types/TabbedContainerProps'; +import { useSettingsContext } from '../SettingsContext'; -const TabbedContainer: React.FC = ({ tabs, themeColor }) => { +const TabbedContainer: React.FC = ({ tabs }) => { const [activeTab, setActiveTab] = useState(0); const [hoveredTab, setHoveredTab] = useState(null); const [tabWidth, setTabWidth] = useState(0); const [position, setPosition] = useState(0); const positionRef = useRef(position); + const themeColor = useSettingsContext().settingsState.customThemeColor; useEffect(() => { const newPosition = -activeTab * 100; @@ -59,24 +61,22 @@ const TabbedContainer: React.FC = ({ tabs, themeColor }) =
-
- -
- {tabs.map((tab, index) => ( -
- {tab.content} -
- ))} -
-
-
+ +
+ {tabs.map((tab, index) => ( +
+ {tab.content} +
+ ))} +
+
); diff --git a/interface/src/hooks/settingsState.ts b/interface/src/hooks/settingsState.ts index 79babb2f..88e63fef 100644 --- a/interface/src/hooks/settingsState.ts +++ b/interface/src/hooks/settingsState.ts @@ -7,22 +7,24 @@ let RanOnce = false; let previousSettingsState: SettingsState const useSettingsState = ({ settingsState, setSettingsState }: SettingsProps) => { - // run the following code once useEffect(() => { if (RanOnce) return; RanOnce = true; // get the current settings state chrome.storage.local.get(function(result: MainConfig) { + console.log(result); setSettingsState({ notificationCollector: result.notificationcollector, lessonAlerts: result.lessonalert, animatedBackground: result.animatedbk, animatedBackgroundSpeed: result.bksliderinput, customThemeColor: result.selectedColor, - betterSEQTAPlus: result.onoff + betterSEQTAPlus: result.onoff, + shortcuts: result.shortcuts, + customshortcuts: result.customshortcuts, }); - + if (result.DarkMode) { document.body.classList.add('dark'); } @@ -36,10 +38,12 @@ const useSettingsState = ({ settingsState, setSettingsState }: SettingsProps) => "bksliderinput": "animatedBackgroundSpeed", "selectedColor": "customThemeColor", "onoff": "betterSEQTAPlus", + "shortcuts": "shortcuts", + "customshortcuts": "customshortcuts", }), []); const storageChangeListener = (changes: chrome.storage.StorageChange) => { - console.log(changes); + console.log(settingsState); for (const [key, { newValue }] of Object.entries(changes)) { if (key === "DarkMode") { if (key === "DarkMode" && newValue) { diff --git a/interface/src/pages/Settings.tsx b/interface/src/pages/Settings.tsx index 68bbb4d6..2c9ab55f 100644 --- a/interface/src/pages/Settings.tsx +++ b/interface/src/pages/Settings.tsx @@ -1,8 +1,10 @@ import Switch from '../components/Switch'; import ColorPicker from '../components/ColorPicker'; -import { SettingsProps, SettingsList } from '../types/SettingsProps'; +import { SettingsList } from '../types/SettingsProps'; +import { useSettingsContext } from '../SettingsContext'; -const Settings: React.FC = ({ settingsState, setSettingsState }) => { +const Settings: React.FC = () => { + const { settingsState, setSettingsState } = useSettingsContext(); const switchChange = (key: string, isOn: boolean) => { setSettingsState({ @@ -52,7 +54,7 @@ const Settings: React.FC = ({ settingsState, setSettingsState }) ]; return ( -
+
{settings.map((setting, index) => (
diff --git a/interface/src/pages/Shortcuts.tsx b/interface/src/pages/Shortcuts.tsx index 7400df6a..6db5faf1 100644 --- a/interface/src/pages/Shortcuts.tsx +++ b/interface/src/pages/Shortcuts.tsx @@ -1,95 +1,96 @@ import { useState } from "react"; import Switch from "../components/Switch"; +import { useSettingsContext } from "../SettingsContext"; + +interface Shortcut { + name: string; + url: string; + enabled?: boolean; +} export default function Shortcuts() { - const [shortcutState, setShortcutState] = useState({ - youtube: false, - outlook: false, - office: false, - spotify: false, - google: false, - duckduckgo: false, - coolmathgames: false, - sace: false, - googlescholar: false, - gmail: false, - netflix: false - }); + const { settingsState, setSettingsState } = useSettingsContext(); - // Handler for Switches - const switchChange = (key: string, isOn: boolean) => { - setShortcutState({ - ...shortcutState, - [key]: isOn, + const switchChange = (shortcutName: string, isOn: boolean): void => { + const updatedShortcuts = settingsState.shortcuts.map((shortcut) => { + if (shortcut.name === shortcutName) { + return { ...shortcut, enabled: isOn }; + } + return shortcut; }); + + setSettingsState({ ...settingsState, shortcuts: updatedShortcuts }); }; - const DefaultShortcuts = [ - { - title: "YouTube", - link: "https://youtube.com", - modifyElement: switchChange('youtube', isOn)} /> - }, - { - title: "Outlook", - link: "https://outlook.office.com/mail/inbox", - modifyElement: switchChange('outlook', isOn)} /> - }, - { - title: "Office", - link: "https://www.office.com/", - modifyElement: switchChange('office', isOn)} /> - }, - { - title: "Spotify", - link: "https://www.spotify.com/", - modifyElement: switchChange('spotify', isOn)} /> - }, - { - title: "Google", - link: "https://www.google.com/", - modifyElement: switchChange('google', isOn)} /> - }, - { - title: "DuckDuckGo", - link: "https://duckduckgo.com/", - modifyElement: switchChange('duckduckgo', isOn)} /> - }, - { - title: "Cool Math Games", - link: "https://www.coolmathgames.com/", - modifyElement: switchChange('coolmathgames', isOn)} /> - }, - { - title: "SACE", - link: "https://www.sace.sa.edu.au/", - modifyElement: switchChange('sace', isOn)} /> - }, - { - title: "Google Scholar", - link: "https://scholar.google.com/", - modifyElement: switchChange('googlescholar', isOn)} /> - }, - { - title: "Gmail", - link: "https://mail.google.com/", - modifyElement: switchChange('gmail', isOn)} /> - }, - { - title: "Netflix", - link: "https://www.netflix.com/", - modifyElement: switchChange('netflix', isOn)} /> + const [newTitle, setNewTitle] = useState(""); + const [newURL, setNewURL] = useState(""); + + const isValidTitle = (title: string): boolean => title.trim() !== ""; + + const isValidURL = (url: string): boolean => { + const pattern = new RegExp("^(https?:\\/\\/)?[\\w.-]+[\\w.-]+$", "i"); + return pattern.test(url); + }; + + const addNewCustomShortcut = (): void => { + if (isValidTitle(newTitle) && isValidURL(newURL)) { + const newShortcut: Shortcut = { name: newTitle.trim(), url: newURL.trim() }; + const updatedCustomShortcuts = [...settingsState.customshortcuts, newShortcut]; + setSettingsState({ ...settingsState, customshortcuts: updatedCustomShortcuts }); + setNewTitle(""); + setNewURL(""); + } else { + // Replace with a more user-friendly way to display errors + console.error("Please enter a valid title and URL."); } - ]; + }; + + const deleteCustomShortcut = (index: number): void => { + const updatedCustomShortcuts = settingsState.customshortcuts.filter((_, i) => i !== index); + setSettingsState({ ...settingsState, customshortcuts: updatedCustomShortcuts }); + }; return (
- {DefaultShortcuts.map((shortcut, index) => ( -
- {shortcut.title} - {shortcut.modifyElement} -
- ))} + {/* Form Section */} +
+ setNewTitle(e.target.value)} + /> + setNewURL(e.target.value)} + /> + +
+ {/* Shortcuts Section */} + {settingsState.shortcuts ? ( + settingsState.shortcuts.map((shortcut) => ( +
+ {shortcut.name} + switchChange(shortcut.name, isOn)} /> +
+ )) + ) : ( +

Loading shortcuts...

+ )} + + {/* Custom Shortcuts Section */} + {settingsState.customshortcuts ? ( + settingsState.customshortcuts.map((shortcut, index) => ( +
+ {shortcut.name} + +
+ )) + ) : ( +

Loading custom shortcuts...

+ )}
); } diff --git a/interface/src/types/AppProps.ts b/interface/src/types/AppProps.ts index b562bd17..e343461d 100644 --- a/interface/src/types/AppProps.ts +++ b/interface/src/types/AppProps.ts @@ -5,25 +5,29 @@ export interface SettingsState { animatedBackgroundSpeed: string; customThemeColor: string; betterSEQTAPlus: boolean; + shortcuts: Shortcut[]; + customshortcuts: CustomShortcut[]; } -// Define the ToggleItem interface for the nested objects in menuitems interface ToggleItem { toggle: boolean; } -// Define the Shortcut interface for the objects in the shortcuts array interface Shortcut { enabled: boolean; name: string; } -// Define the MainConfig interface for the top-level object +interface CustomShortcut { + name: string; + url: string; +} + export interface MainConfig { DarkMode: boolean; animatedbk: boolean; bksliderinput: string; - customshortcuts: any[]; + customshortcuts: CustomShortcut[]; defaultmenuorder: any[]; lessonalert: boolean; menuitems: { @@ -49,5 +53,5 @@ export interface MainConfig { onoff: boolean; selectedColor: string; shortcuts: Shortcut[]; - subjectfilters: Record; // Could be more specific based on what types are allowed + subjectfilters: Record; } diff --git a/interface/src/types/TabbedContainerProps.ts b/interface/src/types/TabbedContainerProps.ts index fee6f452..cea6689d 100644 --- a/interface/src/types/TabbedContainerProps.ts +++ b/interface/src/types/TabbedContainerProps.ts @@ -5,7 +5,6 @@ export interface Tab { } export interface TabbedContainerProps { tabs: Tab[]; - themeColor: string; } declare const TabbedContainer: React.FC; export default TabbedContainer;