add accordion to themeCreator

This commit is contained in:
SethBurkart123
2024-03-31 14:43:42 +11:00
parent 806d2419cb
commit 8ac3b1de71
12 changed files with 59 additions and 25 deletions
@@ -0,0 +1,92 @@
import Switch from '../../components/Switch';
import Slider from '../../components/Slider';
import PickerSwatch from '../../components/PickerSwatch';
import { SettingsList } from '../../types/SettingsProps';
import { useSettingsContext } from '../../SettingsContext';
import browser from 'webextension-polyfill'
const Settings: React.FC = () => {
const { settingsState, setSettingsState } = useSettingsContext();
const switchChange = (key: string, isOn: boolean) => {
setSettingsState({
...settingsState,
[key]: isOn,
});
};
const sliderChange = (key: string, value: number) => {
setSettingsState({
...settingsState,
[key]: value,
});
};
const settings: SettingsList[] = [
{
title: "Transparency Effects",
description: "Enables transparency effects on certain elements such as blur. (May impact battery life)",
modifyElement: <Switch state={settingsState.transparencyEffects} onChange={(isOn: boolean) => switchChange('transparencyEffects', 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: <Slider state={parseInt(settingsState.animatedBackgroundSpeed)} onChange={(value: number) => sliderChange('animatedBackgroundSpeed', value)} />
},
{
title: "Custom Theme Colour",
description: "Customise the overall theme colour of SEQTA Learn.",
modifyElement: <PickerSwatch />
},
{
title: "Telemetry",
description: "Enables/disables error collecting.",
modifyElement: <Switch state={settingsState.telemetry} onChange={(isOn: boolean) => switchChange('telemetry', isOn)} />
},
{
title: "Edit Sidebar Layout",
description: "Customise the sidebar layout.",
modifyElement: <button onClick={() => browser.runtime.sendMessage({ type: 'currentTab', info: 'EditSidebar' })} className='px-4 py-1 text-[0.75rem] dark:bg-[#38373D] bg-[#DDDDDD] dark:text-white rounded-md'>Edit</button>
},
{
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: "BetterSEQTA+",
description: "Enables BetterSEQTA+ features",
modifyElement: <Switch state={settingsState.betterSEQTAPlus} onChange={(isOn: boolean) => switchChange('betterSEQTAPlus', isOn)} />
}
];
return (
<div className="flex flex-col -mt-4 overflow-y-scroll divide-y divide-zinc-100 dark:divide-zinc-700">
{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;
@@ -0,0 +1,148 @@
import { useState, memo, useCallback } from "react";
import Switch from "../../components/Switch";
import { useSettingsContext } from "../../SettingsContext";
import { motion, AnimatePresence } from "framer-motion";
import { CustomShortcut } from "../../types/AppProps";
function formatUrl(inputUrl: string) {
const protocolRegex = /^(http:\/\/|https:\/\/|ftp:\/\/)/;
return protocolRegex.test(inputUrl) ? inputUrl : `https://${inputUrl}`;
}
const Shortcuts = memo(() => {
const { settingsState, setSettingsState } = useSettingsContext();
const [newTitle, setNewTitle] = useState<string>("");
const [isFormVisible, setFormVisible] = useState(false);
const [newURL, setNewURL] = useState<string>("");
const switchChange = useCallback((shortcutName: string, isOn: boolean) => {
setSettingsState((prevState) => {
const updatedShortcuts = prevState.shortcuts.map((shortcut) =>
shortcut.name === shortcutName ? { ...shortcut, enabled: isOn } : shortcut
);
return { ...prevState, shortcuts: updatedShortcuts };
});
}, [setSettingsState]);
const isValidTitle = useCallback((title: string) => title.trim() !== "", []);
const isValidURL = useCallback((url: string) => {
const pattern = new RegExp("^(https?:\\/\\/)?[\\w.-]+[\\w.-]+(/[\\w.-]*)*$", "i");
return pattern.test(url);
}, []);
const addNewCustomShortcut = useCallback(() => {
if (isValidTitle(newTitle) && isValidURL(newURL)) {
const newShortcut: CustomShortcut = { name: newTitle.trim(), url: formatUrl(newURL).trim(), icon: newTitle[0] };
const updatedCustomShortcuts = [...settingsState.customshortcuts, newShortcut];
setSettingsState({ ...settingsState, customshortcuts: updatedCustomShortcuts });
setNewTitle("");
setNewURL("");
setFormVisible(false);
} else {
// Replace with a more user-friendly way to display errors
alert("Please enter a valid title and URL.");
}
}, [newTitle, newURL, isValidTitle, isValidURL, setSettingsState]);
const deleteCustomShortcut = useCallback((index: number) => {
setSettingsState((prevState) => ({
...prevState,
customshortcuts: prevState.customshortcuts.filter((_, i) => i !== index),
}));
}, [setSettingsState]);
const toggleForm = useCallback(() => {
setFormVisible((isVisible) => !isVisible);
}, []);
return (
<div className="flex flex-col divide-y divide-zinc-100 dark:divide-zinc-700">
<AnimatePresence>
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={isFormVisible ? { opacity: 1, height: "auto" } : { opacity: 0, height: 0 }}
exit={{ opacity: 0, height: 0 }}
transition={{ type: "spring", damping: 20 }}
>
{isFormVisible &&
<div className="flex flex-col items-center mb-4">
<motion.input
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
className="w-full p-2 rounded-md bg-zinc-100 dark:bg-zinc-700 focus:outline-none"
type="text"
placeholder="Shortcut Name"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
/>
<motion.input
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
className="w-full p-2 my-2 rounded-md bg-zinc-100 dark:bg-zinc-700 focus:outline-none"
type="text"
placeholder="URL eg. https://google.com"
value={newURL}
onChange={(e) => setNewURL(e.target.value)}
/>
<motion.button
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.4 }}
className="w-full px-4 py-2 text-white bg-blue-500 rounded-md"
onClick={ addNewCustomShortcut }
>
Add
</motion.button>
</div>
}
</motion.div>
</AnimatePresence>
{!isFormVisible && (
<button
className="w-full px-4 py-2 mb-4 text-white bg-blue-500 rounded"
onClick={toggleForm}
>
Add Custom Shortcut
</button>
)}
{/* Shortcuts Section */}
{settingsState.shortcuts ? (
settingsState.shortcuts.map((shortcut, index) => shortcut.name && (
<div className="flex items-center justify-between px-4 py-3" key={index}>
{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={index}>
{shortcut.name}
<button onClick={() => deleteCustomShortcut(index)}>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
))
) : (
<p>Loading custom shortcuts...</p>
)}
</div>
);
});
export default Shortcuts;
@@ -0,0 +1,31 @@
import { FC, useEffect, useState } from 'react';
import BackgroundSelector from '../../components/BackgroundSelector';
import ThemeSelector from '../../components/ThemeSelector';
import { listThemes } from '../../hooks/ThemeManagment';
const Themes: FC = () => {
const [isEditMode, setIsEditMode] = useState<boolean>(false);
const [selectedType, setSelectedType] = useState<'background' | 'theme'>('background');
useEffect(() => {
listThemes().then(themes => {
if (themes.selectedTheme) {
setSelectedType('theme');
} else {
setSelectedType('background');
}
});
}, [])
return (
<div>
<button className="absolute top-12 z-20 right-0 p-2 text-[0.8rem] text-blue-500" onClick={() => setIsEditMode(!isEditMode)}>
{isEditMode ? 'Done' : 'Edit'}
</button>
<BackgroundSelector setSelectedType={setSelectedType} selectedType={selectedType} isEditMode={isEditMode} />
<ThemeSelector setSelectedType={setSelectedType} selectedType={selectedType} isEditMode={isEditMode} />
</div>
);
};
export default Themes;