add basic theme upload functionality

This commit is contained in:
SethBurkart123
2024-05-21 21:36:20 +10:00
parent c9c357bec2
commit 438af68b9f
4 changed files with 129 additions and 4 deletions
+92 -3
View File
@@ -7,6 +7,11 @@ import { CustomTheme, DownloadedTheme } from '../types/CustomThemes';
import { useSettingsContext } from '../SettingsContext';
import { SettingsState } from '../types/AppProps';
import { debounce } from 'lodash';
import { InstallTheme } from '../../seqta/ui/themes/downloadTheme';
import SpinnerIcon from './LoadingSpinner';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import useVisibility from './useVisibility';
interface ThemeSelectorProps {
isEditMode: boolean;
@@ -17,7 +22,14 @@ const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> &
const [themes, setThemes] = useState<Omit<CustomTheme, 'CustomImages'>[]>([]);
const [downloadedThemes, setDownloadedThemes] = useState<DownloadedTheme[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [isDragging, setIsDragging] = useState<boolean>(false);
const [tempTheme, setTempTheme] = useState<any>(null);
const { settingsState, setSettingsState } = useSettingsContext();
const [elementRef, isVisible] = useVisibility({
root: null, // Use the viewport as the root
rootMargin: '0px',
threshold: 0.1, // 10% of the element needs to be visible
});
const setSelectedTheme = (themeId: string) => {
setSettingsState((prevState: SettingsState) => ({
@@ -54,6 +66,19 @@ const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> &
};
}, []);
useEffect(() => {
let intervalId: any;
if (isVisible) {
intervalId = setInterval(fetchThemes, 10000); // Fetch themes every 10 seconds
} else {
clearInterval(intervalId);
}
return () => {
clearInterval(intervalId);
};
}, [isVisible]);
const fetchThemes = async () => {
try {
const { themes, selectedTheme } = await listThemes();
@@ -121,14 +146,72 @@ const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> &
[settingsState.selectedTheme]
);
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => {
setIsDragging(false);
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
const file: File = e.dataTransfer.files[0];
const reader: FileReader = new FileReader();
reader.onload = async (event: ProgressEvent<FileReader>) => {
try {
const result: any = JSON.parse(event.target!.result as string);
try {
setTempTheme(result);
await InstallTheme(result);
await fetchThemes();
setTempTheme(null);
} catch(error) {
toast.error('Invalid file type. Please upload a valid theme file.');
setTempTheme(null);
}
} catch (error) {
toast.error('Error parsing file. Please upload a valid JSON theme file.');
setTempTheme(null);
}
};
reader.readAsText(file);
};
if (isLoading) {
return <div className='text-center'>Loading themes...</div>;
}
return (
<div className="my-3">
<div
ref={elementRef}
className={`my-3 ${isDragging ? '' : ''}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<div className={`${isDragging ? 'opacity-90' : 'opacity-0'} transition absolute w-full h-full p-2 z-50`}>
<div className='w-full h-full shadow-xl bg-black/60 rounded-xl'>
<div className='flex items-center justify-center w-full h-full'>
<div className='flex flex-col items-center justify-center'>
<svg height="48" width="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<g fill="#F7F7F7">
<path d="M44,31a1,1,0,0,0-1,1v8a3,3,0,0,1-3,3H8a3,3,0,0,1-3-3V32a1,1,0,0,0-2,0v8a5.006,5.006,0,0,0,5,5H40a5.006,5.006,0,0,0,5-5V32A1,1,0,0,0,44,31Z" fill="#F7F7F7"/>
<path d="M23.2,33.6a1,1,0,0,0,1.6,0l9-12A1,1,0,0,0,33,20H26V5a2,2,0,0,0-4,0V20H15a1,1,0,0,0-.8,1.6Z" fill="#F7F7F7"/>
</g>
</svg>
<span className='text-lg'>Drop theme here</span>
</div>
</div>
</div>
</div>
<h2 className="pb-2 text-lg font-bold">Themes</h2>
<div className="flex flex-col gap-2">
{themes.map((theme) => (
<ThemeCover
key={theme.id}
@@ -152,10 +235,16 @@ const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> &
/>
))}
{ downloadedThemes.length + themes.length > 0 && <div
{tempTheme && (
<div className="flex justify-center w-full bg-gray-200 rounded-xl dark:bg-zinc-700/50 place-items-center aspect-theme animate-pulse">
<SpinnerIcon className='opacity-50' />
</div>
)}
{downloadedThemes.length + themes.length > 0 && <div
id="divider"
className="w-full h-[1px] my-2 bg-zinc-100 dark:bg-zinc-600"
></div> }
></div>}
<button
onClick={() => browser.tabs.create({ url: browser.runtime.getURL('src/interface/index.html#store')})}
@@ -0,0 +1,34 @@
import { useState, useEffect, useRef } from 'react';
interface Options {
root?: Element | null;
rootMargin?: string;
threshold?: number | number[];
}
type UseVisibilityReturnType = [any | null, boolean];
const useVisibility = (options: Options): UseVisibilityReturnType => {
const [isVisible, setIsVisible] = useState<boolean>(false);
const elementRef = useRef<Element | null>(null);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
}, options);
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => {
if (elementRef.current) {
observer.unobserve(elementRef.current);
}
};
}, [elementRef, options]);
return [elementRef, isVisible];
};
export default useVisibility;
+2
View File
@@ -6,6 +6,7 @@ import { SettingsContextProvider } from '../SettingsContext';
import Shortcuts from './SettingsPage/Shortcuts';
import Picker from '../components/Picker';
import Themes from './SettingsPage/Themes';
import { ToastContainer } from 'react-toastify';
import browser from 'webextension-polyfill';
@@ -31,6 +32,7 @@ const SettingsPage = ({ standalone }: SettingsPage) => {
return (
<SettingsContextProvider>
<ToastContainer />
<div className={`flex flex-col w-[384px] shadow-2xl gap-2 bg-white ${ standalone ? 'h-[600px]' : 'h-[100vh] rounded-xl' } overflow-clip 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" />
+1 -1
View File
@@ -8,7 +8,7 @@ type ThemeSelectorRef = {
const Themes: FC = () => {
const [isEditMode, setIsEditMode] = useState<boolean>(false);
const themeSelectorRef = createRef<ThemeSelectorRef>(); // Add type annotation here
const themeSelectorRef = createRef<ThemeSelectorRef>();
const disableTheme = async () => {
themeSelectorRef?.current?.disableTheme();