add theme saving (This took hours)

This commit is contained in:
SethBurkart123
2024-04-01 20:24:51 +11:00
parent c9431de33f
commit 6c799ba346
8 changed files with 427 additions and 407 deletions
+58
View File
@@ -0,0 +1,58 @@
import React from 'react';
import { CustomTheme } from '../types/CustomThemes';
type ThemeCoverProps = {
theme: Omit<CustomTheme, 'CustomImages'>;
isSelected: boolean;
isEditMode: boolean;
onThemeSelect: (themeId: string) => void;
onThemeDelete: (themeId: string) => void;
};
export const ThemeCover: React.FC<ThemeCoverProps> = ({
theme,
isSelected,
isEditMode,
onThemeSelect,
onThemeDelete,
}) => {
const handleThemeClick = () => {
onThemeSelect(theme.id);
};
const handleDeleteClick = (e: React.MouseEvent) => {
e.stopPropagation();
onThemeDelete(theme.id);
};
return (
<button
className={`relative w-full h-16 flex justify-center items-center rounded-lg bg-zinc-700 transition ring dark:ring-white ring-zinc-300 ${
isSelected ? 'dark:ring-2 ring-4' : 'ring-0'
}`}
onClick={handleThemeClick}
>
{isEditMode && (
<div
className="absolute top-0 right-0 z-10 flex w-6 h-6 p-2 text-white translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full place-items-center"
onClick={handleDeleteClick}
>
<div className="w-4 h-0.5 bg-white"></div>
</div>
)}
<div className="relative top-0 z-10 flex justify-center w-full h-full overflow-hidden text-white transition rounded-lg group place-items-center bg-zinc-200 dark:bg-zinc-900">
{/* Render theme cover image or placeholder */}
{/* {theme.CustomImages.length > 0 ? (
<img
src={URL.createObjectURL(theme.CustomImages[0].blob)}
alt={theme.name}
className="absolute inset-0 z-0 object-cover"
/>
) : (
<div className="absolute inset-0 z-0 bg-gray-300 rounded-lg"></div>
)} */}
<div className="z-10">{theme.name}</div>
</div>
</button>
);
};
+85 -156
View File
@@ -1,177 +1,106 @@
import { memo, useEffect, useState } from "react";
import themesList from '../assets/themes';
import { listThemes, disableTheme, downloadTheme, setTheme, deleteTheme } from "../hooks/ThemeManagment";
import Browser from "webextension-polyfill";
interface Theme {
name: string;
url: string;
isDownloaded: boolean;
isLoading: boolean;
coverImage: JSX.Element;
}
import React, { useEffect, useState, useCallback } from 'react';
import { listThemes, deleteTheme, setTheme, disableTheme } from '../hooks/ThemeManagment';
import { ThemeCover } from './ThemeCover';
import Browser from 'webextension-polyfill';
import { CustomTheme } from '../types/CustomThemes';
interface ThemeSelectorProps {
selectedType: "background" | "theme";
setSelectedType: (type: "background" | "theme") => void;
setSelectedType: React.Dispatch<React.SetStateAction<'background' | 'theme'>>;
selectedType: 'background' | 'theme';
isEditMode: boolean;
}
const ThemeSelector = ({ selectedType, setSelectedType, isEditMode }: ThemeSelectorProps) => {
const [themes, setThemes] = useState<Theme[]>([]);
const [enabledThemeName, setEnabledThemeName] = useState<string>('');
useEffect(() => {
const initializeThemes = async () => {
const downloaded = (await listThemes());
const initializedThemes = themesList.map(theme => ({
...theme,
isDownloaded: downloaded.themes.includes(theme.name),
isLoading: false
}));
if (downloaded.selectedTheme !== '') {
setEnabledThemeName(downloaded.selectedTheme);
}
initializedThemes.sort((a, b) => Number(b.isDownloaded) - Number(a.isDownloaded));
setThemes(initializedThemes);
};
initializeThemes();
}, []);
const handleDeleteTheme = async (themeName: string) => {
await deleteTheme(themeName);
setThemes(prevThemes => {
// Update the theme's isDownloaded property to false
const updatedThemes = prevThemes.map(theme => {
if (theme.name === themeName) {
return { ...theme, isDownloaded: false };
}
return theme;
});
// Sort themes so non-downloaded ones appear last
updatedThemes.sort((a, b) => Number(b.isDownloaded) - Number(a.isDownloaded));
return updatedThemes;
});
// Reset the enabled theme name if the deleted theme was the currently enabled one
if (enabledThemeName === themeName) {
setEnabledThemeName('');
setSelectedType('background');
}
};
const handleThemeAction = async (themeName: string, themeURL: string) => {
const startLoading = (name: string) => (
setThemes(prevThemes => prevThemes.map(theme =>
theme.name === name ? { ...theme, isLoading: true } : theme
))
);
// Stop loading for the selected theme.
const stopLoading = (name: string) => (
setThemes(prevThemes => prevThemes.map(theme =>
theme.name === name ? { ...theme, isLoading: false } : theme
))
);
// Update the theme as downloaded.
const markAsDownloaded = (name: string) => (
setThemes(prevThemes => prevThemes.map(theme =>
theme.name === name ? { ...theme, isDownloaded: true } : theme
))
);
startLoading(themeName);
// Early return if theme is not found.
const theme = themes.find(t => t.name === themeName);
if (!theme) {
stopLoading(themeName);
return;
}
// If theme is downloaded and is the currently enabled theme, disable it.
if (theme.isDownloaded && themeName === enabledThemeName) {
await disableTheme();
setEnabledThemeName('');
setSelectedType('background');
stopLoading(themeName);
return;
}
// If theme is downloaded but not enabled, enable it.
if (theme.isDownloaded && themeName !== enabledThemeName) {
await setTheme(themeName, themeURL);
setEnabledThemeName(themeName);
setSelectedType('theme');
stopLoading(themeName);
return;
}
// If theme is not downloaded, download and enable it.
if (!theme.isDownloaded) {
await downloadTheme(themeName, themeURL);
markAsDownloaded(themeName);
setSelectedType('theme');
setEnabledThemeName(themeName);
}
stopLoading(themeName);
};
const ThemeSelector: React.FC<ThemeSelectorProps> = ({
setSelectedType,
selectedType,
isEditMode,
}) => {
const [themes, setThemes] = useState<Omit<CustomTheme, 'CustomImages'>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [selectedThemeId, setSelectedThemeId] = useState<string | null>(null);
useEffect(() => {
if (selectedType === 'background') {
setEnabledThemeName('');
setSelectedThemeId(null);
}
}, [selectedType]);
useEffect(() => {
const fetchThemes = async () => {
try {
const { themes, selectedTheme } = await listThemes();
console.log(await listThemes());
setThemes(themes);
setSelectedThemeId(selectedTheme ? selectedTheme : null);
} catch (error) {
console.error('Error fetching themes:', error);
} finally {
setIsLoading(false);
}
};
fetchThemes();
}, []);
const handleThemeSelect = useCallback(
async (themeId: string) => {
if (themeId === selectedThemeId) {
await disableTheme();
setSelectedThemeId(null);
setSelectedType('background');
} else {
const selectedTheme = themes.find((theme) => theme.id === themeId);
if (selectedTheme) {
await setTheme(selectedTheme.id);
setSelectedThemeId(themeId);
setSelectedType('theme');
}
}
},
[selectedThemeId, themes, setSelectedType]
);
const handleThemeDelete = useCallback(
async (themeId: string) => {
try {
await deleteTheme(themeId);
setThemes((prevThemes) => prevThemes.filter((theme) => theme.id !== themeId));
if (themeId === selectedThemeId) {
setSelectedThemeId(null);
setSelectedType('background');
}
} catch (error) {
console.error('Error deleting theme:', error);
}
},
[selectedThemeId, setSelectedType]
);
if (isLoading) {
return <div>Loading themes...</div>;
}
return (
<div className="my-2">
{(isEditMode ? themes.some(theme => theme.isDownloaded) : themes.length > 0) && (
<h2 className="pb-2 text-lg font-bold">Themes</h2>
)}
<div className="flex flex-col gap-4">
{themes
.filter(theme => !isEditMode || theme.isDownloaded) // Only show downloaded themes in edit mode
.map((theme) => (
<button
key={theme.name}
className={`relative w-full h-16 flex justify-center items-center rounded-lg bg-zinc-700 transition ring dark:ring-white ring-zinc-300 ${enabledThemeName == theme.name && selectedType == "theme" ? 'dark:ring-2 ring-4' : 'ring-0'}`}
onClick={() => handleThemeAction(theme.name, theme.url)}
disabled={theme.isLoading}
>
{isEditMode && (
<div className="absolute top-0 right-0 z-10 flex w-6 h-6 p-2 text-white translate-x-1/2 -translate-y-1/2 bg-red-600 rounded-full place-items-center"
onClick={(e) => { e.stopPropagation(); handleDeleteTheme(theme.name); }}>
<div className="w-4 h-0.5 bg-white"></div>
</div>
)}
<div className={`relative transition rounded-lg overflow-hidden top-0 z-10 flex justify-center w-full h-full text-white group place-items-center ${ theme.isDownloaded ? '' : 'hover:bg-black/20'}`}>
<span className="absolute z-10 text-3xl transition opacity-0 font-IconFamily group-hover:opacity-100">
{ theme.isDownloaded || theme.isLoading ? '' : ''}
</span>
{ theme.isLoading &&
<div className="z-10 inline-block w-6 h-6 border-4 border-current rounded-full animate-spin border-t-transparent" role="status">
<span className="sr-only">Loading...</span>
</div> }
</div>
<div className="absolute inset-0 z-0 overflow-hidden rounded-lg">
{theme.coverImage}
</div>
</button>
{themes.map((theme) => (
<ThemeCover
key={theme.id}
theme={theme}
isSelected={theme.id === selectedThemeId}
isEditMode={isEditMode}
onThemeSelect={handleThemeSelect}
onThemeDelete={handleThemeDelete}
/>
))}
<button
onClick={() => Browser.runtime.sendMessage({ type: 'currentTab', info: 'OpenThemeCreator' })}
className="flex items-center justify-center w-full h-16 transition rounded-lg bg-zinc-700">
className="flex items-center justify-center w-full h-16 transition rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white"
>
<span className="text-xl font-IconFamily">{'\uec60'}</span>
<span className="ml-2">Create Theme</span>
</button>
@@ -180,4 +109,4 @@ const ThemeSelector = ({ selectedType, setSelectedType, isEditMode }: ThemeSelec
);
};
export default memo(ThemeSelector);
export default ThemeSelector;
+36 -12
View File
@@ -1,9 +1,6 @@
import { debounce } from 'lodash';
import browser from 'webextension-polyfill'
interface ThemeList {
themes: string[];
selectedTheme: string;
}
import { CustomTheme, ThemeList } from '../types/CustomThemes';
export const downloadTheme = async (themeName: string, themeURL: string) => {
// send message to the background script
@@ -17,23 +14,32 @@ export const downloadTheme = async (themeName: string, themeURL: string) => {
});
}
export const setTheme = async (themeName: string, themeURL: string) => {
export const setTheme = async (themeID: string) => {
// send message to the background script
await browser.runtime.sendMessage({
type: 'currentTab',
info: 'SetTheme',
body: {
themeName: themeName,
themeURL: themeURL
themeID: themeID
}
});
}
export const listThemes = async () => {
export const listThemes = async (): Promise<ThemeList> => {
// send message to the background script
const response: ThemeList = await browser.runtime.sendMessage({
const response: ThemeList = await new Promise((resolve, reject) => {
browser.runtime.sendMessage({
type: 'currentTab',
info: 'ListThemes'
}).then((response) => {
if (response) {
resolve(response);
} else {
reject(new Error('Failed to get response'));
}
}).catch((error: any) => {
reject(error);
});
});
return response;
@@ -46,20 +52,22 @@ export const disableTheme = async () => {
});
};
export const deleteTheme = async (themeName: string) => {
export const deleteTheme = async (themeID: string) => {
await browser.runtime.sendMessage({
type: 'currentTab',
info: 'DeleteTheme',
body: {
themeName: themeName
themeID: themeID
}
});
}
export const sendThemeUpdate = debounce((updatedTheme: CustomTheme) => {
export const sendThemeUpdate = debounce((updatedTheme: CustomTheme, saveTheme?: boolean) => {
// Create a copy of the updatedTheme object
const updatedThemeCopy: CustomTheme = { ...updatedTheme };
saveTheme = saveTheme || false;
// Convert image blobs to base64
const base64ConversionPromises = updatedThemeCopy.CustomImages.map(async (image) => {
const base64 = await blobToBase64(image.blob);
@@ -76,6 +84,7 @@ export const sendThemeUpdate = debounce((updatedTheme: CustomTheme) => {
type: 'currentTab',
info: 'UpdateThemePreview',
body: updatedThemeCopy,
save: saveTheme,
});
})
.catch((error) => {
@@ -97,3 +106,18 @@ const blobToBase64 = (blob: Blob): Promise<string> => {
reader.readAsDataURL(blob);
});
};
export const enableCurrentTheme = async () => {
await browser.runtime.sendMessage({
type: 'currentTab',
info: 'EnableCurrentTheme',
});
};
export const saveUpdatedTheme = async (updatedTheme: CustomTheme) => {
await browser.runtime.sendMessage({
type: 'currentTab',
info: 'SaveTheme',
body: updatedTheme,
});
};
+3 -3
View File
@@ -9,7 +9,7 @@ const Themes: FC = () => {
useEffect(() => {
listThemes().then(themes => {
if (themes.selectedTheme) {
if (themes?.selectedTheme) {
setSelectedType('theme');
} else {
setSelectedType('background');
@@ -18,12 +18,12 @@ const Themes: FC = () => {
}, [])
return (
<div>
<div className="px-0.5">
<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} />
<ThemeSelector selectedType={selectedType} setSelectedType={setSelectedType} isEditMode={isEditMode} />
</div>
);
};
+3 -18
View File
@@ -7,6 +7,7 @@ import { sendThemeUpdate } from '../hooks/ThemeManagment';
import { XMarkIcon } from '@heroicons/react/24/outline';
import localforage from 'localforage';
import { v4 as uuidv4 } from 'uuid';
import { CustomTheme } from '../types/CustomThemes';
function ThemeCreator({ themeID }: { themeID?: string }) {
const [theme, setTheme] = useState<CustomTheme>({
@@ -77,24 +78,8 @@ function ThemeCreator({ themeID }: { themeID?: string }) {
}));
}
const saveTheme = async () => {
try {
await localforage.setItem(theme.id, theme);
await localforage.getItem('customThemes').then((themes: unknown) => {
const themeList = themes as string[] | null;
if (themeList) {
if (!themeList.includes(theme.id)) {
themeList.push(theme.id);
localforage.setItem('customThemes', themeList);
}
} else {
localforage.setItem('customThemes', [theme.id]);
}
});
console.log('Theme saved successfully!');
} catch (error) {
console.error('Error saving theme:', error);
}
const saveTheme = () => {
sendThemeUpdate(theme, true);
};
useEffect(() => {
+17 -2
View File
@@ -1,4 +1,4 @@
type CustomTheme = {
export type CustomTheme = {
id: string;
name: string;
description: string;
@@ -8,8 +8,23 @@ type CustomTheme = {
CustomImages: CustomImage[];
}
type CustomImage = {
export type CustomImage = {
id: string;
blob: Blob;
variableName: string;
}
export type CustomImageBase64 = {
id: string;
url: string;
variableName: string;
}
export type CustomThemeBase64 = Omit<CustomTheme, 'CustomImages'> & {
CustomImages: CustomImageBase64[];
}
export type ThemeList = {
themes: Omit<CustomTheme, 'CustomImages'>[];
selectedTheme: string;
}
+161 -162
View File
@@ -1,169 +1,97 @@
import browser from 'webextension-polyfill'
import localforage from 'localforage';
let currentThemeClass = '';
// Utility function to fetch and parse JSON
const fetchJSON = async (url: any) => {
const res = await fetch(url, {cache: 'no-store'});
return await res.json();
};
// Utility function to fetch and parse text
const fetchText = async (url: any) => {
const res = await fetch(url);
return await res.text();
};
// Check if the theme already exists in IndexedDB
const themeExistsInDB = async (themeName: any) => {
return (await localforage.getItem(`css_${themeName}`)) !== null;
};
// Fetch theme details (CSS, images, className, darkMode, defaultColour) from a given URL
const fetchThemeJSON = async (url: any) => {
const { css, images, className, darkMode, defaultColour } = await fetchJSON(url);
const cssText = await fetchText(css);
return { css: cssText, images, className, darkMode, defaultColour };
};
// Save individual image to IndexedDB
const saveImageToDB = async (themeName: any, cssVar: any, imageUrl: any) => {
try {
const response = await fetch(imageUrl);
if (!response.ok) throw new Error(response.statusText);
const blob = await response.blob();
await localforage.setItem(`images_${themeName}_${cssVar}`, blob);
} catch (error) {
console.error(`Failed to save image for ${cssVar}: ${error}`);
}
};
// Save theme details to storage via localForage
const saveToIndexedDB = async (theme: any, themeName: any) => {
await localforage.setItem(`css_${themeName}`, theme);
await Promise.all(Object.entries(theme.images).map(([cssVar, imageUrl]) => saveImageToDB(themeName, cssVar, imageUrl)));
};
declare global {
interface Window {
currentThemeStyle: any;
currentThemeClass: any;
}
}
// Apply theme from storage via localForage to document, including dark mode and default color
const applyTheme = async (themeName: any) => {
const { css, className, images, darkMode, defaultColour }: any = await localforage.getItem(`css_${themeName}`);
const newStyle = document.createElement('style');
newStyle.innerHTML = css;
document.head.appendChild(newStyle);
if (window.currentThemeStyle) {
document.head.removeChild(window.currentThemeStyle);
}
window.currentThemeStyle = newStyle;
if (window.currentThemeClass) {
document.body.classList.remove(window.currentThemeClass);
}
if (className) {
document.body.classList.add(className);
window.currentThemeClass = className;
}
if (images) {
await Promise.all(
Object.keys(images).map(async (cssVar) => {
const imageData: any = await localforage.getItem(`images_${themeName}_${cssVar}`);
const objectURL = URL.createObjectURL(imageData);
document.documentElement.style.setProperty(cssVar, `url(${objectURL})`);
})
);
}
browser.storage.local.set({ DarkMode: darkMode, selectedColor: defaultColour });
};
export const listThemes = async () => {
const themes = await localforage.keys();
return {
themes: themes.filter((key) => key.startsWith('css_')).map((key) => key.replace('css_', '')),
selectedTheme: await localforage.getItem('selectedTheme')
};
};
export const downloadTheme = async (themeName: any, themeUrl: any) => {
const themeData = await fetchThemeJSON(themeUrl);
await saveToIndexedDB(themeData, themeName);
await setTheme(themeName, themeUrl);
};
export const deleteTheme = async (themeName: any) => {
const currentTheme = await localforage.getItem('selectedTheme');
if (currentTheme === themeName) {
await disableTheme();
}
await localforage.removeItem(`css_${themeName}`);
await Promise.all(
(await localforage.keys()).filter((key) => key.startsWith(`images_${themeName}`)).map((key) => localforage.removeItem(key))
);
};
export const setTheme = async (themeName: any, themeUrl: any) => {
if (!(await themeExistsInDB(themeName))) {
await downloadTheme(themeName, themeUrl);
}
await localforage.setItem('selectedTheme', themeName);
await applyTheme(themeName).catch((error) => {
console.error(`Failed to apply theme: ${error}`);
});
};
export const enableCurrentTheme = async () => {
const currentTheme = await localforage.getItem('selectedTheme');
if (currentTheme) {
await applyTheme(currentTheme).catch((error) => {
console.error(`Failed to apply current theme: ${error}`);
});
}
};
export const disableTheme = async () => {
// Remove current theme's style if it exists
if (window.currentThemeStyle) {
document.head.removeChild(window.currentThemeStyle);
window.currentThemeStyle = null;
}
// Remove current theme's class if it exists
if (currentThemeClass) {
document.body.classList.remove(currentThemeClass);
currentThemeClass = '';
}
// Remove any applied image URLs from the root element
const currentTheme = await localforage.getItem('selectedTheme');
if (currentTheme) {
const themeData: any = await localforage.getItem(`css_${currentTheme}`);
if (themeData && themeData.images) {
Object.keys(themeData.images).forEach(cssVar => {
document.documentElement.style.removeProperty(cssVar);
});
}
}
// Clear the selected theme from localforage
localforage.removeItem('selectedTheme');
};
import { CustomImageBase64, CustomTheme, CustomThemeBase64, ThemeList } from '../../interface/types/CustomThemes';
const imageData: Record<string, { url: string; variableName: string }> = {};
export const UpdateThemePreview = async (updatedTheme: CustomTheme) => {
export const enableCurrentTheme = async () => {
const themeId = await browser.storage.local.get('selectedTheme') as { selectedTheme: string };
if (themeId.selectedTheme) {
const theme = await localforage.getItem(themeId.selectedTheme) as CustomTheme;
if (theme) {
await applyTheme(theme);
}
}
}
export const deleteTheme = async (themeId: string) => {
try {
await localforage.removeItem(themeId);
const themeIds = await localforage.getItem('customThemes') as string[] | null;
if (themeIds) {
const updatedThemeIds = themeIds.filter((id) => id !== themeId);
await localforage.setItem('customThemes', updatedThemeIds);
}
console.log('Theme deleted successfully!');
} catch (error) {
console.error('Error deleting theme:', error);
}
};
export const getAvailableThemes = async (): Promise<ThemeList | {}> => {
try {
const themeIds = await localforage.getItem('customThemes') as string[] | null;
console.log('Available themes:', themeIds);
if (themeIds) {
const themes = await Promise.all(
themeIds.map(async (id) => {
const theme = await localforage.getItem(id) as CustomTheme;
const { CustomImages, ...themeWithoutImages } = theme;
return themeWithoutImages;
})
);
const selectedTheme = await browser.storage.local.get('selectedTheme') as { selectedTheme: string };
return { themes, selectedTheme: selectedTheme.selectedTheme ? selectedTheme.selectedTheme : '' };
}
return {
themes: [],
selectedTheme: '',
};
} catch (error) {
console.error('Error getting available themes:', error);
return {
themes: [],
selectedTheme: ''
};
}
};
export const saveTheme = async (theme: CustomThemeBase64) => {
try {
const updatedTheme: CustomTheme = {
...theme,
CustomImages: await Promise.all(
theme.CustomImages.map(async (image) => ({
id: image.id,
blob: await fetch(image.url).then((res) => res.blob()),
variableName: image.variableName,
}))
),
};
console.log('Theme to save:', updatedTheme)
await localforage.setItem(updatedTheme.id, updatedTheme);
await localforage.getItem('customThemes').then((themes: unknown) => {
const themeList = themes as string[] | null;
if (themeList) {
if (!themeList.includes(updatedTheme.id)) {
themeList.push(updatedTheme.id);
localforage.setItem('customThemes', themeList);
}
} else {
localforage.setItem('customThemes', [updatedTheme.id]);
}
});
console.log('Theme saved successfully!');
} catch (error) {
console.error('Error saving theme:', error);
}
};
export const UpdateThemePreview = async (updatedTheme: CustomThemeBase64) => {
const { CustomCSS, CustomImages, defaultColour } = updatedTheme;
// Update image data
@@ -225,7 +153,7 @@ function removeImageFromDocument(variableName: string) {
document.documentElement.style.removeProperty('--' + variableName);
}
export function updateImage(image: CustomImage) {
export function updateImage(image: CustomImageBase64) {
// Extract base64 data from the data URI
const base64Index = image.url.indexOf(',') + 1;
const imageBase64 = image.url.substring(base64Index);
@@ -244,3 +172,74 @@ export function updateImage(image: CustomImage) {
return imageUrl;
}
const applyTheme = async (theme: CustomTheme) => {
const { CustomCSS, CustomImages, defaultColour } = theme;
// Apply custom CSS
applyCustomCSS(CustomCSS);
// Apply default color
if (defaultColour !== '') {
browser.storage.local.set({ selectedColor: defaultColour });
}
// Apply custom images
CustomImages.forEach((image) => {
const imageUrl = URL.createObjectURL(image.blob);
document.documentElement.style.setProperty('--' + image.variableName, `url(${imageUrl})`);
});
};
const removeTheme = (theme: CustomTheme) => {
// Remove custom CSS
const styleElement = document.getElementById('theme-preview-styles');
if (styleElement) {
styleElement.parentNode?.removeChild(styleElement);
}
// Reset default color
//browser.storage.local.set({ selectedColor: '' });
// Remove custom images
const customImageVariables = theme.CustomImages.map((image) => image.variableName);
customImageVariables.forEach((variableName) => {
document.documentElement.style.removeProperty('--' + variableName);
});
};
export const setTheme = async (themeId: string) => {
try {
const enabledTheme = await browser.storage.local.get('selectedTheme') as { selectedTheme: string };
const theme = await localforage.getItem(themeId) as CustomTheme;
// Remove the currently enabled theme
if (enabledTheme.selectedTheme) {
const currentTheme = await localforage.getItem(enabledTheme.selectedTheme) as CustomTheme;
if (currentTheme) {
removeTheme(currentTheme);
}
}
await applyTheme(theme);
await browser.storage.local.set({ selectedTheme: themeId });
} catch (error) {
console.error('Error setting theme:', error);
}
}
export const disableTheme = async () => {
try {
const enabledTheme = await browser.storage.local.get('selectedTheme') as { selectedTheme: string };
if (enabledTheme.selectedTheme) {
const theme = await localforage.getItem(enabledTheme.selectedTheme) as CustomTheme;
if (theme) {
removeTheme(theme);
}
}
await browser.storage.local.set({ selectedTheme: '' });
} catch (error) {
console.error('Error disabling theme:', error);
}
}
+34 -24
View File
@@ -1,7 +1,7 @@
import browser from 'webextension-polyfill'
import { MenuOptionsOpen, OpenMenuOptions, OpenWhatsNewPopup, closeSettings } from '../../../SEQTA';
import { deleteTheme, disableTheme, downloadTheme, listThemes, setTheme, UpdateThemePreview } from '../../ui/Themes';
import { UpdateThemePreview, deleteTheme, disableTheme, enableCurrentTheme, getAvailableThemes, saveTheme, setTheme } from '../../ui/Themes';
import { OpenThemeCreator } from '../../ui/ThemeCreator';
export class MessageHandler {
@@ -10,50 +10,61 @@ export class MessageHandler {
}
routeMessage(request: any, _sender: any, sendResponse: any) {
switch (request.info) {
case 'EditSidebar':
this.editSidebar();
closeSettings();
sendResponse({ status: 'success' });
break;
/* Theme related */
case 'UpdateThemePreview':
if (request?.save == true) {
saveTheme(request.body).then(() => {
setTheme(request.body.themeID).then(() => {
sendResponse({ status: 'success' });
});
});
} else {
UpdateThemePreview(request.body);
sendResponse({ status: 'success' });
}
break;
case 'SaveTheme':
saveTheme(request.body).then(() => {
sendResponse({ status: 'success' });
});
break;
case 'SetTheme':
console.log(request);
setTheme(request.body.themeName, request.body.themeURL).then(() => {
setTheme(request.body.themeID).then(() => {
sendResponse({ status: 'success' });
});
return true;
case 'DownloadTheme':
downloadTheme(request.body.themeName, request.body.themeURL).then(() => {
sendResponse({ status: 'success' });
});
return true;
case 'ListThemes':
listThemes().then((response) => {
sendResponse(response);
});
return true;
break;
case 'DisableTheme':
disableTheme().then(() => {
sendResponse({ status: 'success' });
});
return true;
break;
case 'DeleteTheme':
deleteTheme(request.body.themeName).then(() => {
sendResponse({ status: 'success' });
});
return true;
case 'UpdateThemePreview':
UpdateThemePreview(request.body).then(() => {
deleteTheme(request.body.themeID).then(() => {
sendResponse({ status: 'success' });
});
break;
case 'ListThemes':
getAvailableThemes().then((themes) => {
sendResponse(themes);
});
return true;
case 'OpenChangelog':
OpenWhatsNewPopup();
closeSettings();
sendResponse({ status: 'success' });
break;
case 'OpenThemeCreator':
OpenThemeCreator();
closeSettings();
@@ -62,7 +73,6 @@ export class MessageHandler {
default:
console.log('Unknown request info:', request.info);
}
}