custom shortcut support in popup

This commit is contained in:
SethBurkart123
2023-09-25 09:32:09 +10:00
parent f5cc56c9d9
commit 86380b4ee0
9 changed files with 178 additions and 138 deletions
+6 -17
View File
@@ -1,31 +1,18 @@
// App.tsx import React from 'react';
import React, { useState } from 'react';
import TabbedContainer from './components/TabbedContainer'; import TabbedContainer from './components/TabbedContainer';
import Settings from './pages/Settings'; import Settings from './pages/Settings';
import logo from './assets/betterseqta-dark-full.png'; import logo from './assets/betterseqta-dark-full.png';
import logoDark from './assets/betterseqta-light-full.png'; import logoDark from './assets/betterseqta-light-full.png';
import Shortcuts from './pages/Shortcuts'; import Shortcuts from './pages/Shortcuts';
import About from './pages/About'; import About from './pages/About';
import { SettingsContextProvider } from './SettingsContext';
import type { SettingsState } from './types/AppProps';
import useSettingsState from './hooks/settingsState';
const App: React.FC = () => { const App: React.FC = () => {
const [settingsState, setSettingsState] = useState<SettingsState>({
notificationCollector: false,
lessonAlerts: false,
animatedBackground: false,
animatedBackgroundSpeed: "0",
customThemeColor: "#db6969",
betterSEQTAPlus: true
});
useSettingsState({ settingsState, setSettingsState });
const tabs = [ const tabs = [
{ {
title: 'Settings', title: 'Settings',
content: <Settings settingsState={settingsState} setSettingsState={setSettingsState} /> content: <Settings />
}, },
{ {
title: 'Shortcuts', title: 'Shortcuts',
@@ -38,13 +25,15 @@ const App: React.FC = () => {
]; ];
return ( return (
<SettingsContextProvider>
<div className="flex flex-col w-[384px] shadow-2xl gap-2 bg-white rounded-xl h-[590px] dark:bg-zinc-800 dark:text-white"> <div className="flex flex-col w-[384px] shadow-2xl gap-2 bg-white rounded-xl h-[590px] dark:bg-zinc-800 dark:text-white">
<div className="grid border-b border-b-zinc-200/40 place-items-center"> <div className="grid border-b border-b-zinc-200/40 place-items-center">
<img src={logo} className="w-4/5 dark:hidden" /> <img src={logo} className="w-4/5 dark:hidden" />
<img src={logoDark} className="hidden w-4/5 dark:block" /> <img src={logoDark} className="hidden w-4/5 dark:block" />
</div> </div>
<TabbedContainer themeColor={settingsState.customThemeColor} tabs={tabs} /> <TabbedContainer tabs={tabs} />
</div> </div>
</SettingsContextProvider>
); );
}; };
+39
View File
@@ -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<React.SetStateAction<SettingsState>>;
} | undefined>(undefined);
export const SettingsContextProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [settingsState, setSettingsState] = useState<SettingsState>({
notificationCollector: false,
lessonAlerts: false,
animatedBackground: false,
animatedBackgroundSpeed: "0",
customThemeColor: "#db6969",
betterSEQTAPlus: true,
shortcuts: []
});
useSettingsState({ settingsState, setSettingsState });
return (
<SettingsContext.Provider value={{ settingsState, setSettingsState }}>
{children}
</SettingsContext.Provider>
);
};
// 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;
};
+4 -2
View File
@@ -22,18 +22,20 @@ const Picker = ({ color, onChange }: ColorPickerProps) => {
}, [showPicker]); }, [showPicker]);
return ( return (
<div className=""> <>
<button <button
onClick={() => setShowPicker(!showPicker)} onClick={() => setShowPicker(!showPicker)}
style={{ background: color }} style={{ background: color }}
className="w-16 h-8 rounded-md" className="w-16 h-8 rounded-md"
></button> ></button>
{showPicker && ( {showPicker && (
<div className="absolute top-0 left-0 w-full h-full bg-black/20">
<div ref={ref} className="fixed top-0 left-0 z-50 p-4 bg-white border rounded-lg shadow-lg border-zinc-00"> <div ref={ref} className="fixed top-0 left-0 z-50 p-4 bg-white border rounded-lg shadow-lg border-zinc-00">
<ColorPicker value={color} onChange={onChange} /> <ColorPicker value={color} onChange={onChange} />
</div> </div>
)}
</div> </div>
)}
</>
); );
}; };
+3 -3
View File
@@ -1,13 +1,15 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import type { TabbedContainerProps } from '../types/TabbedContainerProps'; import type { TabbedContainerProps } from '../types/TabbedContainerProps';
import { useSettingsContext } from '../SettingsContext';
const TabbedContainer: React.FC<TabbedContainerProps> = ({ tabs, themeColor }) => { const TabbedContainer: React.FC<TabbedContainerProps> = ({ tabs }) => {
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [hoveredTab, setHoveredTab] = useState<number | null>(null); const [hoveredTab, setHoveredTab] = useState<number | null>(null);
const [tabWidth, setTabWidth] = useState(0); const [tabWidth, setTabWidth] = useState(0);
const [position, setPosition] = useState(0); const [position, setPosition] = useState(0);
const positionRef = useRef(position); const positionRef = useRef(position);
const themeColor = useSettingsContext().settingsState.customThemeColor;
useEffect(() => { useEffect(() => {
const newPosition = -activeTab * 100; const newPosition = -activeTab * 100;
@@ -59,7 +61,6 @@ const TabbedContainer: React.FC<TabbedContainerProps> = ({ tabs, themeColor }) =
</div> </div>
</div> </div>
<div className="h-full px-4 overflow-y-scroll overflow-x-clip"> <div className="h-full px-4 overflow-y-scroll overflow-x-clip">
<div className="-mt-4">
<motion.div <motion.div
initial={false} initial={false}
animate={{ x: `${position}%` }} animate={{ x: `${position}%` }}
@@ -77,7 +78,6 @@ const TabbedContainer: React.FC<TabbedContainerProps> = ({ tabs, themeColor }) =
</div> </div>
</motion.div> </motion.div>
</div> </div>
</div>
</> </>
); );
}; };
+7 -3
View File
@@ -7,20 +7,22 @@ let RanOnce = false;
let previousSettingsState: SettingsState let previousSettingsState: SettingsState
const useSettingsState = ({ settingsState, setSettingsState }: SettingsProps) => { const useSettingsState = ({ settingsState, setSettingsState }: SettingsProps) => {
// run the following code once
useEffect(() => { useEffect(() => {
if (RanOnce) return; if (RanOnce) return;
RanOnce = true; RanOnce = true;
// get the current settings state // get the current settings state
chrome.storage.local.get(function(result: MainConfig) { chrome.storage.local.get(function(result: MainConfig) {
console.log(result);
setSettingsState({ setSettingsState({
notificationCollector: result.notificationcollector, notificationCollector: result.notificationcollector,
lessonAlerts: result.lessonalert, lessonAlerts: result.lessonalert,
animatedBackground: result.animatedbk, animatedBackground: result.animatedbk,
animatedBackgroundSpeed: result.bksliderinput, animatedBackgroundSpeed: result.bksliderinput,
customThemeColor: result.selectedColor, customThemeColor: result.selectedColor,
betterSEQTAPlus: result.onoff betterSEQTAPlus: result.onoff,
shortcuts: result.shortcuts,
customshortcuts: result.customshortcuts,
}); });
if (result.DarkMode) { if (result.DarkMode) {
@@ -36,10 +38,12 @@ const useSettingsState = ({ settingsState, setSettingsState }: SettingsProps) =>
"bksliderinput": "animatedBackgroundSpeed", "bksliderinput": "animatedBackgroundSpeed",
"selectedColor": "customThemeColor", "selectedColor": "customThemeColor",
"onoff": "betterSEQTAPlus", "onoff": "betterSEQTAPlus",
"shortcuts": "shortcuts",
"customshortcuts": "customshortcuts",
}), []); }), []);
const storageChangeListener = (changes: chrome.storage.StorageChange) => { const storageChangeListener = (changes: chrome.storage.StorageChange) => {
console.log(changes); console.log(settingsState);
for (const [key, { newValue }] of Object.entries(changes)) { for (const [key, { newValue }] of Object.entries(changes)) {
if (key === "DarkMode") { if (key === "DarkMode") {
if (key === "DarkMode" && newValue) { if (key === "DarkMode" && newValue) {
+5 -3
View File
@@ -1,8 +1,10 @@
import Switch from '../components/Switch'; import Switch from '../components/Switch';
import ColorPicker from '../components/ColorPicker'; import ColorPicker from '../components/ColorPicker';
import { SettingsProps, SettingsList } from '../types/SettingsProps'; import { SettingsList } from '../types/SettingsProps';
import { useSettingsContext } from '../SettingsContext';
const Settings: React.FC<SettingsProps> = ({ settingsState, setSettingsState }) => { const Settings: React.FC = () => {
const { settingsState, setSettingsState } = useSettingsContext();
const switchChange = (key: string, isOn: boolean) => { const switchChange = (key: string, isOn: boolean) => {
setSettingsState({ setSettingsState({
@@ -52,7 +54,7 @@ const Settings: React.FC<SettingsProps> = ({ settingsState, setSettingsState })
]; ];
return ( return (
<div className="flex flex-col overflow-y-scroll divide-y divide-zinc-100"> <div className="flex flex-col -mt-4 overflow-y-scroll divide-y divide-zinc-100">
{settings.map((setting, index) => ( {settings.map((setting, index) => (
<div className="flex items-center justify-between px-4 py-3" key={index}> <div className="flex items-center justify-between px-4 py-3" key={index}>
<div className="pr-4"> <div className="pr-4">
+80 -79
View File
@@ -1,95 +1,96 @@
import { useState } from "react"; import { useState } from "react";
import Switch from "../components/Switch"; import Switch from "../components/Switch";
import { useSettingsContext } from "../SettingsContext";
interface Shortcut {
name: string;
url: string;
enabled?: boolean;
}
export default function Shortcuts() { export default function Shortcuts() {
const [shortcutState, setShortcutState] = useState({ const { settingsState, setSettingsState } = useSettingsContext();
youtube: false,
outlook: false, const switchChange = (shortcutName: string, isOn: boolean): void => {
office: false, const updatedShortcuts = settingsState.shortcuts.map((shortcut) => {
spotify: false, if (shortcut.name === shortcutName) {
google: false, return { ...shortcut, enabled: isOn };
duckduckgo: false, }
coolmathgames: false, return shortcut;
sace: false,
googlescholar: false,
gmail: false,
netflix: false
}); });
// Handler for Switches setSettingsState({ ...settingsState, shortcuts: updatedShortcuts });
const switchChange = (key: string, isOn: boolean) => {
setShortcutState({
...shortcutState,
[key]: isOn,
});
}; };
const DefaultShortcuts = [ const [newTitle, setNewTitle] = useState<string>("");
{ const [newURL, setNewURL] = useState<string>("");
title: "YouTube",
link: "https://youtube.com", const isValidTitle = (title: string): boolean => title.trim() !== "";
modifyElement: <Switch state={shortcutState.youtube} onChange={(isOn: boolean) => switchChange('youtube', isOn)} />
}, const isValidURL = (url: string): boolean => {
{ const pattern = new RegExp("^(https?:\\/\\/)?[\\w.-]+[\\w.-]+$", "i");
title: "Outlook", return pattern.test(url);
link: "https://outlook.office.com/mail/inbox", };
modifyElement: <Switch state={shortcutState.outlook} onChange={(isOn: boolean) => switchChange('outlook', isOn)} />
}, const addNewCustomShortcut = (): void => {
{ if (isValidTitle(newTitle) && isValidURL(newURL)) {
title: "Office", const newShortcut: Shortcut = { name: newTitle.trim(), url: newURL.trim() };
link: "https://www.office.com/", const updatedCustomShortcuts = [...settingsState.customshortcuts, newShortcut];
modifyElement: <Switch state={shortcutState.office} onChange={(isOn: boolean) => switchChange('office', isOn)} /> setSettingsState({ ...settingsState, customshortcuts: updatedCustomShortcuts });
}, setNewTitle("");
{ setNewURL("");
title: "Spotify", } else {
link: "https://www.spotify.com/", // Replace with a more user-friendly way to display errors
modifyElement: <Switch state={shortcutState.spotify} onChange={(isOn: boolean) => switchChange('spotify', isOn)} /> console.error("Please enter a valid title and URL.");
},
{
title: "Google",
link: "https://www.google.com/",
modifyElement: <Switch state={shortcutState.google} onChange={(isOn: boolean) => switchChange('google', isOn)} />
},
{
title: "DuckDuckGo",
link: "https://duckduckgo.com/",
modifyElement: <Switch state={shortcutState.duckduckgo} onChange={(isOn: boolean) => switchChange('duckduckgo', isOn)} />
},
{
title: "Cool Math Games",
link: "https://www.coolmathgames.com/",
modifyElement: <Switch state={shortcutState.coolmathgames} onChange={(isOn: boolean) => switchChange('coolmathgames', isOn)} />
},
{
title: "SACE",
link: "https://www.sace.sa.edu.au/",
modifyElement: <Switch state={shortcutState.sace} onChange={(isOn: boolean) => switchChange('sace', isOn)} />
},
{
title: "Google Scholar",
link: "https://scholar.google.com/",
modifyElement: <Switch state={shortcutState.googlescholar} onChange={(isOn: boolean) => switchChange('googlescholar', isOn)} />
},
{
title: "Gmail",
link: "https://mail.google.com/",
modifyElement: <Switch state={shortcutState.gmail} onChange={(isOn: boolean) => switchChange('gmail', isOn)} />
},
{
title: "Netflix",
link: "https://www.netflix.com/",
modifyElement: <Switch state={shortcutState.netflix} onChange={(isOn: boolean) => switchChange('netflix', isOn)} />
} }
]; };
const deleteCustomShortcut = (index: number): void => {
const updatedCustomShortcuts = settingsState.customshortcuts.filter((_, i) => i !== index);
setSettingsState({ ...settingsState, customshortcuts: updatedCustomShortcuts });
};
return ( return (
<div className="flex flex-col divide-y divide-zinc-100"> <div className="flex flex-col divide-y divide-zinc-100">
{DefaultShortcuts.map((shortcut, index) => ( {/* Form Section */}
<div className="flex items-center justify-between px-4 py-3" key={index}> <div className="flex items-center justify-between px-4 py-3">
{shortcut.title} <input
{shortcut.modifyElement} type="text"
placeholder="Title"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
/>
<input
type="text"
placeholder="URL"
value={newURL}
onChange={(e) => setNewURL(e.target.value)}
/>
<button onClick={addNewCustomShortcut}>Add</button>
</div> </div>
))} {/* Shortcuts Section */}
{settingsState.shortcuts ? (
settingsState.shortcuts.map((shortcut) => (
<div className="flex items-center justify-between px-4 py-3" key={shortcut.name}>
{shortcut.name}
<Switch state={shortcut.enabled} onChange={(isOn) => switchChange(shortcut.name, isOn)} />
</div>
))
) : (
<p>Loading shortcuts...</p>
)}
{/* Custom Shortcuts Section */}
{settingsState.customshortcuts ? (
settingsState.customshortcuts.map((shortcut, index) => (
<div className="flex items-center justify-between px-4 py-3" key={shortcut.name}>
{shortcut.name}
<button onClick={() => deleteCustomShortcut(index)}>Delete</button>
</div>
))
) : (
<p>Loading custom shortcuts...</p>
)}
</div> </div>
); );
} }
+9 -5
View File
@@ -5,25 +5,29 @@ export interface SettingsState {
animatedBackgroundSpeed: string; animatedBackgroundSpeed: string;
customThemeColor: string; customThemeColor: string;
betterSEQTAPlus: boolean; betterSEQTAPlus: boolean;
shortcuts: Shortcut[];
customshortcuts: CustomShortcut[];
} }
// Define the ToggleItem interface for the nested objects in menuitems
interface ToggleItem { interface ToggleItem {
toggle: boolean; toggle: boolean;
} }
// Define the Shortcut interface for the objects in the shortcuts array
interface Shortcut { interface Shortcut {
enabled: boolean; enabled: boolean;
name: string; name: string;
} }
// Define the MainConfig interface for the top-level object interface CustomShortcut {
name: string;
url: string;
}
export interface MainConfig { export interface MainConfig {
DarkMode: boolean; DarkMode: boolean;
animatedbk: boolean; animatedbk: boolean;
bksliderinput: string; bksliderinput: string;
customshortcuts: any[]; customshortcuts: CustomShortcut[];
defaultmenuorder: any[]; defaultmenuorder: any[];
lessonalert: boolean; lessonalert: boolean;
menuitems: { menuitems: {
@@ -49,5 +53,5 @@ export interface MainConfig {
onoff: boolean; onoff: boolean;
selectedColor: string; selectedColor: string;
shortcuts: Shortcut[]; shortcuts: Shortcut[];
subjectfilters: Record<string, any>; // Could be more specific based on what types are allowed subjectfilters: Record<string, any>;
} }
@@ -5,7 +5,6 @@ export interface Tab {
} }
export interface TabbedContainerProps { export interface TabbedContainerProps {
tabs: Tab[]; tabs: Tab[];
themeColor: string;
} }
declare const TabbedContainer: React.FC<TabbedContainerProps>; declare const TabbedContainer: React.FC<TabbedContainerProps>;
export default TabbedContainer; export default TabbedContainer;