major updates to popup dev, manifest fix

This commit is contained in:
Seth Burkart
2023-09-15 12:01:08 +10:00
parent 979e67f622
commit e9db7b0283
34 changed files with 416 additions and 146 deletions
Executable
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

+75
View File
@@ -0,0 +1,75 @@
// App.tsx
import { useState } 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';
export interface SettingsState {
notificationCollector: boolean;
lessonAlerts: boolean;
animatedBackground: boolean;
animatedBackgroundSpeed: boolean;
customThemeColor: string;
betterSEQTAPlus: boolean;
}
const App: React.FC = () => {
const [settingsState, setSettingsState] = useState<SettingsState>({
notificationCollector: false,
lessonAlerts: false,
animatedBackground: false,
animatedBackgroundSpeed: false,
customThemeColor: "#db6969",
betterSEQTAPlus: true
});
// Handler for Switches
const switchChange = (key: string, isOn: boolean) => {
setSettingsState({
...settingsState,
[key]: isOn,
});
};
// Handler for ColorPicker
const colorChange = (color: string) => {
setSettingsState({
...settingsState,
customThemeColor: color,
});
};
const tabs = [
{
title: 'Settings',
content: <Settings settingsState={settingsState} switchChange={switchChange} colorChange={colorChange} />
},
{
title: 'Shortcuts',
content: <Shortcuts />
},
{
title: 'About',
content: <About />
}
];
return (
<div className="flex justify-center w-screen h-screen pt-4 overflow-hidden" style={{ background: settingsState.customThemeColor }}>
<div className="flex flex-col w-[24rem] shadow-2xl gap-2 bg-white rounded-xl h-4/6 dark:bg-zinc-800 dark:text-white">
<div className="grid border-b border-b-zinc-200/40 place-items-center">
<img src={logo} className="w-4/5 dark:hidden" />
<img src={logoDark} className="hidden w-4/5 dark:block" />
</div>
<TabbedContainer themeColor={settingsState.customThemeColor} tabs={tabs} />
</div>
</div>
);
}
export default App;

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

+45
View File
@@ -0,0 +1,45 @@
// TODO: Create types for ColorPicker
// @ts-expect-error No typescript declarations available
import ColorPicker from 'react-best-gradient-color-picker';
import { useState, useRef, useEffect } from 'react';
interface ColorPickerProps {
color: string;
onChange: (color: string) => void;
}
const Picker = ({ color, onChange }: ColorPickerProps) => {
const [showPicker, setShowPicker] = useState<boolean>(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
if (ref.current && !ref.current.contains(event.target as Node)) {
setShowPicker(false);
}
};
if (showPicker) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [showPicker]);
return (
<div className="">
<button
onClick={() => setShowPicker(!showPicker)}
style={{ background: color }}
className="w-16 h-8 rounded-md"
></button>
{showPicker && (
<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} />
</div>
)}
</div>
);
};
export default Picker;
@@ -1,4 +1,3 @@
.switch[data-ison="true"] {
justify-content: end;
background-color: #30D259;
}
@@ -1,29 +1,28 @@
import { useState } from "react";
import { motion } from "framer-motion";
import "./Switch.css";
interface SwitchProps {
onChange: (isOn: boolean) => void;
state: boolean;
}
export default function Switch(props: SwitchProps) {
const [isOn, setIsOn] = useState(false);
const toggleSwitch = () => {
const newIsOn = !isOn;
setIsOn(newIsOn);
const newIsOn = !props.state;
props.onChange(newIsOn);
};
return (
<div
className="flex w-14 p-1 cursor-pointer rounded-full dark:bg-[#38373D] bg-[#DDDDDD] switch"
data-isOn={isOn}
data-isOn={props.state}
onClick={toggleSwitch}
>
<motion.div
className="w-6 h-6 bg-white dark:bg-[#FEFEFE] rounded-full drop-shadow-md"
layout
initial={{ x: props.state ? 0 : 0 }}
animate={{ x: props.state ? 24 : 0 }}
transition={spring}
/>
</div>
@@ -0,0 +1,92 @@
import React, { useState, useRef, useEffect } from 'react';
import { motion } from 'framer-motion';
interface Tab {
title: string;
content: JSX.Element;
}
interface TabbedContainerProps {
tabs: Tab[];
themeColor: string;
}
const TabbedContainer: React.FC<TabbedContainerProps> = ({ tabs, themeColor }) => {
const [activeTab, setActiveTab] = useState(0);
const [hoveredTab, setHoveredTab] = useState<number | null>(null);
const [tabWidth, setTabWidth] = useState(0);
const [position, setPosition] = useState(0);
const positionRef = useRef(position);
useEffect(() => {
const newPosition = -activeTab * 100;
setPosition(newPosition);
positionRef.current = newPosition;
}, [activeTab]);
const containerRef = useRef(null);
const springTransition = { type: 'spring', stiffness: 250, damping: 25 };
useEffect(() => {
if (containerRef.current) {
// @ts-expect-error for some reason its giving an error in TS but it works...
const width = containerRef.current.getBoundingClientRect().width;
setTabWidth(width / tabs.length);
}
}, [tabs.length]);
const calcXPos = (index: number | null) => {
if (index !== null) {
return tabWidth * index;
}
return tabWidth * activeTab;
};
return (
<div className="h-full px-4 overflow-y-scroll overflow-x-clip">
<div ref={containerRef} className="sticky top-0 z-10 text-[0.875rem] mb-2 pb-2 bg-white">
<div className="relative flex">
<motion.div
className="absolute top-0 left-0 z-0 h-full rounded-full opacity-40"
style={{ width: `${tabWidth}px`, background: themeColor }}
initial={false}
animate={{ x: calcXPos(hoveredTab) }}
transition={springTransition}
/>
{tabs.map((tab, index) => (
<button
key={index}
className="relative z-10 flex-1 px-4 py-2"
onClick={() => setActiveTab(index)}
onMouseEnter={() => setHoveredTab(index)}
onMouseLeave={() => setHoveredTab(null)}
>
{tab.title}
</button>
))}
</div>
</div>
<div className="relative">
<motion.div
initial={false}
animate={{ x: `${position}%` }}
transition={springTransition}
>
<div className="absolute flex w-full" style={{ left: `${-position}%` }}>
{tabs.map((tab, index) => (
<div
key={index}
className={`w-full ${activeTab === index ? '' : 'hidden'}`}
>
{tab.content}
</div>
))}
</div>
</motion.div>
</div>
</div>
);
};
export default TabbedContainer;
+10
View File
@@ -0,0 +1,10 @@
const About: React.FC = () => {
return (
<div className="flex flex-col overflow-y-scroll divide-y divide-zinc-100">
<h2>About</h2>
</div>
);
};
export default About;
+68
View File
@@ -0,0 +1,68 @@
import Switch from '../components/Switch';
import ColorPicker from '../components/ColorPicker';
import { SettingsState } from '../App';
interface ISetting {
title: string;
description: string;
modifyElement: JSX.Element;
}
interface SettingsProps {
settingsState: SettingsState;
switchChange: (key: string, isOn: boolean) => void;
colorChange: (color: string) => void;
}
const Settings: React.FC<SettingsProps> = ({ settingsState, switchChange, colorChange }) => {
const settings: ISetting[] = [
{
title: "Notification Collector",
description: "Uncaps the 9+ limit for notifications, showing the real number.",
modifyElement: <Switch state={settingsState.notificationCollector} onChange={(isOn: boolean) => switchChange('notificationCollector', isOn)} />
},
{
title: "Lesson Alerts",
description: "Sends a native browser notification ~5 minutes prior to lessons.",
modifyElement: <Switch state={settingsState.lessonAlerts} onChange={(isOn: boolean) => switchChange('lessonAlerts', isOn)} />
},
{
title: "Animated Background",
description: "Adds an animated background to BetterSEQTA. (May impact battery life)",
modifyElement: <Switch state={settingsState.animatedBackground} onChange={(isOn: boolean) => switchChange('animatedBackground', isOn)} />
},
{
title: "Animated Background Speed",
description: "Controls the speed of the animated background.",
modifyElement: <Switch state={settingsState.animatedBackgroundSpeed} onChange={(isOn: boolean) => switchChange('animatedBackgroundSpeed', isOn)} />
},
{
title: "Custom Theme Colour",
description: "Customise the overall theme colour of SEQTA Learn.",
modifyElement: <ColorPicker color={settingsState.customThemeColor} onChange={(color: string) => colorChange(color)} />
},
{
title: "BetterSEQTA+",
description: "Unlocks premium features.",
modifyElement: <Switch state={settingsState.betterSEQTAPlus} onChange={(isOn: boolean) => switchChange('betterSEQTAPlus', isOn)} />
}
];
return (
<div className="flex flex-col overflow-y-scroll divide-y divide-zinc-100">
{settings.map((setting, index) => (
<div className="flex items-center justify-between px-4 py-3" key={index}>
<div className="pr-4">
<h2 className="text-sm font-bold">{setting.title}</h2>
<p className="text-xs">{setting.description}</p>
</div>
<div>
{setting.modifyElement}
</div>
</div>
))}
</div>
);
};
export default Settings;
+95
View File
@@ -0,0 +1,95 @@
import { useState } from "react";
import Switch from "../components/Switch";
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
});
// Handler for Switches
const switchChange = (key: string, isOn: boolean) => {
setShortcutState({
...shortcutState,
[key]: isOn,
});
};
const DefaultShortcuts = [
{
title: "YouTube",
link: "https://youtube.com",
modifyElement: <Switch state={shortcutState.youtube} onChange={(isOn: boolean) => switchChange('youtube', isOn)} />
},
{
title: "Outlook",
link: "https://outlook.office.com/mail/inbox",
modifyElement: <Switch state={shortcutState.outlook} onChange={(isOn: boolean) => switchChange('outlook', isOn)} />
},
{
title: "Office",
link: "https://www.office.com/",
modifyElement: <Switch state={shortcutState.office} onChange={(isOn: boolean) => switchChange('office', isOn)} />
},
{
title: "Spotify",
link: "https://www.spotify.com/",
modifyElement: <Switch state={shortcutState.spotify} onChange={(isOn: boolean) => switchChange('spotify', isOn)} />
},
{
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)} />
}
];
return (
<div className="flex flex-col divide-y divide-zinc-100">
{DefaultShortcuts.map((shortcut, index) => (
<div className="flex items-center justify-between px-4 py-3" key={index}>
{shortcut.title}
{shortcut.modifyElement}
</div>
))}
</div>
);
}
+22
View File
@@ -0,0 +1,22 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
theme: {
fontSize: {
'xs': '.65rem',
'sm': '.775rem',
'base': '0.65rem', // 16px
'md': '0.65rem', // 16px
'lg': '1rem', // 18px
'xl': '1.25rem', // 20px
'2xl': '1.5rem', // 24px
'3xl': '1.875rem', // 30px
}
},
plugins: [],
}
@@ -5,7 +5,7 @@ import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: '../../public/popup-dist',
//outDir: '../../public/popup-dist',
rollupOptions: {
output: {
assetFileNames: 'client/rsc/[ext]/[name][extname]',
+1 -2
View File
@@ -20,8 +20,7 @@
"permissions": ["tabs", "notifications", "storage"],
"host_permissions": ["https://newsapi.org/", "*://*/*"],
"background": {
"content_scripts": "background.js",
"persistent": false
"service_worker": "background.js"
},
"optional_permissions": ["declarativeContent"],
"content_scripts": [
-67
View File
@@ -1,67 +0,0 @@
import './App.css'
import Switch from './components/Switch'
import logo from './assets/betterseqta-dark-full.png'
import logoDark from './assets/betterseqta-light-full.png'
import ColorPicker from './components/ColorPicker'
const switchChange = (isOn: boolean) => {
console.log(isOn)
}
const settings = [
{
title: "Notification Collector",
description: "Uncaps the 9+ limit for notifications, showing the real number.",
modifyElement: <Switch onChange={switchChange} />
},
{
title: "Lesson Alerts",
description: "Sends a native browser notification ~5 minutes prior to lessons.",
modifyElement: <Switch onChange={switchChange} />
},
{
title: "Animated Background",
description: "Adds an animated background to BetterSEQTA. (May impact battery life)",
modifyElement: <Switch onChange={switchChange} />
},
{
title: "Animated Background Speed",
description: "Controls the speed of the animated background.",
modifyElement: <Switch onChange={switchChange} />
},
{
title: "Custom Theme Colour",
description: "Customise the overall theme colour of SEQTA Learn.",
modifyElement: <ColorPicker />
},
{
title: "BetterSEQTA+",
description: "Unlocks premium features.",
modifyElement: <Switch onChange={switchChange} />
}
]
function App() {
return (
<div className="flex flex-col gap-2 dark:bg-zinc-800">
<div className="grid border-b border-b-zinc-200 place-items-center h-1/2">
<img src={logo} className="w-4/5 dark:hidden" />
<img src={logoDark} className="hidden w-4/5 dark:block" />
</div>
{settings.map((setting, index) => (
<div className="flex justify-between px-4 place-items-center" key={index}>
<div className="dark:text-white">
<h2 className="text-sm font-bold">{setting.title}</h2>
<p className="text-xs">{setting.description}</p>
</div>
<div>
{setting.modifyElement}
</div>
</div>
))}
</div>
)
}
export default App
-43
View File
@@ -1,43 +0,0 @@
// TODO: Create types for ColorPicker
// @ts-expect-error No typescript declarations available
import ColorPicker from 'react-best-gradient-color-picker';
import { useState, useRef, useEffect } from 'react';
const Picker = (): JSX.Element => {
const [color, setColor] = useState<string>('rgba(255,20,255,1)');
const [showPicker, setShowPicker] = useState<boolean>(false);
const pickerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {
setShowPicker(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [pickerRef]);
return (
<div className="relative" ref={pickerRef}>
<button
onClick={() => setShowPicker(!showPicker)}
style={{
'background': color
}}
className="w-16 h-8 rounded-md"
></button>
<div className="absolute top-0 right-0 z-10 p-2 bg-white border rounded-lg shadow-2xl border-zinc-200">
{ showPicker &&
<ColorPicker value={color} onChange={setColor} />
}
</div>
</div>
);
};
export default Picker;
-24
View File
@@ -1,24 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
theme: {
extend: {
colors: {
"background-primary-dark": "",
"background-primary-light": "",
"background-secondary-dark": "",
"background-secondary-light": "",
"forground-primary-dark": "",
"forground-primary-light": "",
"forground-secondary-dark": "",
"forground-secondary-light": ""
},
},
},
plugins: [],
}