mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 11:44:40 +00:00
refactor: remove legacy theme handling and streamline plugin initialization
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
export const imageData: Record<string, { url: string; variableName: string }> = {};
|
||||
|
||||
export function applyCustomCSS(customCSS: string) {
|
||||
let styleElement = document.getElementById('custom-theme');
|
||||
if (!styleElement) {
|
||||
styleElement = document.createElement('style');
|
||||
styleElement.id = 'custom-theme';
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
styleElement.textContent = customCSS;
|
||||
}
|
||||
|
||||
export function removeImageFromDocument(variableName: string) {
|
||||
document.documentElement.style.removeProperty('--' + variableName);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
import type { LoadedCustomTheme } from '@/types/CustomThemes';
|
||||
import { applyCustomCSS, removeImageFromDocument } from './Themes';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
let previousImageVariableNames: string[] = [];
|
||||
let originalColor: string | null = null;
|
||||
let originalTheme: boolean | null = null;
|
||||
|
||||
export const UpdateThemePreview = async (updatedTheme: LoadedCustomTheme) => {
|
||||
const { CustomCSS, CustomImages, defaultColour, forceDark } = updatedTheme;
|
||||
|
||||
// Update dark mode setting
|
||||
if (forceDark !== undefined) {
|
||||
// Store the original theme if it hasn't been stored yet
|
||||
if (originalTheme === null) {
|
||||
originalTheme = settingsState.DarkMode;
|
||||
}
|
||||
settingsState.DarkMode = forceDark;
|
||||
}
|
||||
|
||||
// Get the new image variable names
|
||||
const newImageVariableNames = CustomImages.map(image => image.variableName);
|
||||
|
||||
// Remove images that are no longer present
|
||||
previousImageVariableNames.forEach(variableName => {
|
||||
if (!newImageVariableNames.includes(variableName)) {
|
||||
removeImageFromDocument(variableName);
|
||||
}
|
||||
});
|
||||
|
||||
// Update or add new images
|
||||
CustomImages.forEach((image: any) => {
|
||||
document.documentElement.style.setProperty(`--${image.variableName}`, `url(${image.url})`);
|
||||
});
|
||||
|
||||
// Update the previousImageVariableNames for the next run
|
||||
previousImageVariableNames = newImageVariableNames;
|
||||
|
||||
// Apply custom CSS
|
||||
applyCustomCSS(CustomCSS);
|
||||
|
||||
// Apply default color
|
||||
if (defaultColour) {
|
||||
// Store the original color if it hasn't been stored yet
|
||||
if (originalColor == null) {
|
||||
originalColor = settingsState.selectedColor;
|
||||
}
|
||||
settingsState.selectedColor = defaultColour;
|
||||
}
|
||||
};
|
||||
|
||||
export const ClearThemePreview = () => {
|
||||
previousImageVariableNames.forEach(variableName => {
|
||||
removeImageFromDocument(variableName);
|
||||
});
|
||||
|
||||
previousImageVariableNames = [];
|
||||
|
||||
let styleElement = document.getElementById('custom-theme');
|
||||
if (styleElement) {
|
||||
styleElement.remove();
|
||||
}
|
||||
|
||||
// Reset the color to the original value
|
||||
if (originalColor != null) {
|
||||
settingsState.selectedColor = originalColor;
|
||||
originalColor = settingsState.originalSelectedColor;
|
||||
}
|
||||
|
||||
// Reset the theme (dark/light mode) to the original value
|
||||
if (originalTheme != null) {
|
||||
settingsState.DarkMode = originalTheme;
|
||||
originalTheme = null;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { CustomImage, CustomTheme } from '@/types/CustomThemes';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
import { applyCustomCSS } from './Themes';
|
||||
|
||||
|
||||
export const applyTheme = async (theme: CustomTheme, reEnable?: boolean) => {
|
||||
let CustomCSS = '';
|
||||
let CustomImages: CustomImage[] = [];
|
||||
|
||||
if (theme?.CustomCSS) CustomCSS = theme.CustomCSS;
|
||||
if (theme?.CustomImages) CustomImages = theme.CustomImages;
|
||||
if (theme?.forceDark != undefined) {
|
||||
if (!reEnable) settingsState.originalDarkMode = settingsState.DarkMode
|
||||
|
||||
settingsState.DarkMode = theme.forceDark
|
||||
}
|
||||
|
||||
// Apply custom CSS
|
||||
applyCustomCSS(CustomCSS);
|
||||
|
||||
// Apply custom images
|
||||
CustomImages.forEach((image) => {
|
||||
const imageUrl = URL.createObjectURL(image.blob);
|
||||
document.documentElement.style.setProperty('--' + image.variableName, `url(${imageUrl})`);
|
||||
});
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { CustomTheme } from '@/types/CustomThemes';
|
||||
import { removeTheme } from './removeTheme';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
|
||||
export const deleteTheme = async (themeId: string) => {
|
||||
try {
|
||||
const theme = await localforage.getItem(themeId) as CustomTheme;
|
||||
removeTheme(theme);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
settingsState.selectedTheme = ''
|
||||
} catch (error) {
|
||||
console.error('Error deleting theme:', error);
|
||||
}
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { CustomTheme } from '@/types/CustomThemes';
|
||||
import { removeTheme } from './removeTheme';
|
||||
import { Mutex } from '@/seqta/utils/mutex';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
const mutex = new Mutex();
|
||||
let isDisabling = false;
|
||||
|
||||
export const disableTheme = async () => {
|
||||
if (isDisabling) return;
|
||||
|
||||
if (!settingsState.selectedTheme || settingsState.selectedTheme === '') {
|
||||
console.debug('Theme is already disabled, exit early')
|
||||
// Theme is already disabled, exit early
|
||||
return;
|
||||
}
|
||||
isDisabling = true;
|
||||
const unlock = await mutex.lock();
|
||||
try {
|
||||
if (settingsState.selectedTheme) {
|
||||
console.debug('Disabling theme:', settingsState.selectedTheme);
|
||||
const theme = await localforage.getItem(settingsState.selectedTheme) as CustomTheme;
|
||||
if (theme) {
|
||||
await removeTheme(theme);
|
||||
}
|
||||
}
|
||||
|
||||
settingsState.selectedTheme = ''
|
||||
settingsState.selectedColor = settingsState.originalSelectedColor;
|
||||
} catch (error) {
|
||||
console.error('Error disabling theme:', error);
|
||||
} finally {
|
||||
unlock();
|
||||
isDisabling = false;
|
||||
}
|
||||
};
|
||||
@@ -1,79 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import base64ToBlob from '@/seqta/utils/base64ToBlob';
|
||||
|
||||
type Theme = {
|
||||
name: string;
|
||||
description: string;
|
||||
coverImage: string;
|
||||
marqueeImage: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
type ThemeContent = {
|
||||
id: string;
|
||||
name: string;
|
||||
coverImage: string; // base64
|
||||
description: string;
|
||||
defaultColour: string;
|
||||
CanChangeColour: boolean;
|
||||
CustomCSS: string;
|
||||
hideThemeName: boolean;
|
||||
images: { id: string, variableName: string, data: string }[]; // data: base64
|
||||
};
|
||||
|
||||
function stripBase64Prefix(base64String: string): string {
|
||||
if (!base64String) return '';
|
||||
|
||||
const prefixRegex = /^data:[^;]+;base64,/;
|
||||
try {
|
||||
// Check if the string actually has a base64 prefix
|
||||
if (prefixRegex.test(base64String)) {
|
||||
return base64String.replace(prefixRegex, '');
|
||||
}
|
||||
// If no prefix found, return the original string (assuming it's already base64)
|
||||
return base64String;
|
||||
} catch(err) {
|
||||
console.error('Error stripping base64 prefix:', err);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export const StoreDownloadTheme = async (theme: { themeContent: Theme }) => {
|
||||
if (!theme.themeContent.id) return;
|
||||
|
||||
const themeContent = await fetch(`https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes/${theme.themeContent.id}/theme.json`);
|
||||
const themeData = await themeContent.json() as ThemeContent;
|
||||
|
||||
await InstallTheme(themeData);
|
||||
};
|
||||
|
||||
export const InstallTheme = async (themeData: ThemeContent) => {
|
||||
const strippedCoverImage = stripBase64Prefix(themeData.coverImage);
|
||||
|
||||
const coverImageBlob = base64ToBlob(strippedCoverImage);
|
||||
|
||||
const images = themeData.images.map((image) => ({
|
||||
...image,
|
||||
blob: base64ToBlob(stripBase64Prefix(image.data))
|
||||
}));
|
||||
|
||||
let availableThemes = await localforage.getItem('customThemes') as string[];
|
||||
if (availableThemes && !availableThemes.includes(themeData.id)) {
|
||||
availableThemes.push(themeData.id);
|
||||
} else if (!availableThemes) {
|
||||
availableThemes = [themeData.id];
|
||||
}
|
||||
await localforage.setItem('customThemes', availableThemes);
|
||||
|
||||
await localforage.setItem(themeData.id, {
|
||||
...themeData,
|
||||
webURL: themeData.id,
|
||||
coverImage: coverImageBlob,
|
||||
CustomImages: themeData.images.map((image) => {
|
||||
return {
|
||||
...image,
|
||||
blob: images.find((img) => image.id === img.id)?.blob
|
||||
};
|
||||
})
|
||||
});
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { CustomTheme } from '@/types/CustomThemes';
|
||||
import { applyTheme } from './applyTheme';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
|
||||
export const enableCurrentTheme = async () => {
|
||||
if (settingsState.selectedTheme) {
|
||||
const theme = await localforage.getItem(settingsState.selectedTheme) as CustomTheme;
|
||||
if (theme) {
|
||||
await applyTheme(theme, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { CustomTheme, ThemeList } from '@/types/CustomThemes';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
export const getAvailableThemes = async (): Promise<ThemeList> => {
|
||||
try {
|
||||
const themeIds = await localforage.getItem('customThemes') as string[] | null;
|
||||
if (themeIds) {
|
||||
const themes = await Promise.all(
|
||||
themeIds.map(async (id) => {
|
||||
const theme = await localforage.getItem(id) as CustomTheme;
|
||||
return theme;
|
||||
})
|
||||
);
|
||||
|
||||
return { themes, selectedTheme: settingsState.selectedTheme ? settingsState.selectedTheme : '' };
|
||||
}
|
||||
return {
|
||||
themes: [],
|
||||
selectedTheme: '',
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting available themes:', error);
|
||||
return {
|
||||
themes: [],
|
||||
selectedTheme: ''
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { LoadedCustomTheme } from '@/types/CustomThemes';
|
||||
|
||||
|
||||
export const getTheme = async (themeId: string): Promise<LoadedCustomTheme | null> => {
|
||||
try {
|
||||
const theme = await localforage.getItem(themeId) as LoadedCustomTheme;
|
||||
|
||||
return theme;
|
||||
} catch (error) {
|
||||
console.error('Error getting theme:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { CustomTheme } from '@/types/CustomThemes';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
export const removeTheme = async (theme: CustomTheme) => {
|
||||
// Remove custom CSS
|
||||
const styleElement = document.getElementById('custom-theme');
|
||||
if (styleElement) {
|
||||
styleElement.parentNode?.removeChild(styleElement);
|
||||
}
|
||||
|
||||
const selectedTheme = await localforage.getItem(theme.id) as CustomTheme;
|
||||
localforage.setItem(theme.id, {
|
||||
...selectedTheme,
|
||||
selectedColor: settingsState.selectedColor
|
||||
})
|
||||
|
||||
// Reset default color
|
||||
if (settingsState.originalSelectedColor !== '') {
|
||||
settingsState.selectedColor = settingsState.originalSelectedColor
|
||||
}
|
||||
|
||||
if (settingsState.originalDarkMode !== undefined) {
|
||||
settingsState.DarkMode = settingsState.originalDarkMode
|
||||
settingsState.originalDarkMode = undefined
|
||||
}
|
||||
|
||||
// Remove custom images
|
||||
const customImageVariables = theme.CustomImages.map((image) => image.variableName);
|
||||
customImageVariables.forEach((variableName) => {
|
||||
const blobUrl = document.documentElement.style.getPropertyValue('--' + variableName);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
|
||||
document.documentElement.style.removeProperty('--' + variableName);
|
||||
});
|
||||
};
|
||||
@@ -1,30 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { LoadedCustomTheme } from '@/types/CustomThemes';
|
||||
import { disableTheme } from './disableTheme';
|
||||
import { themeUpdates } from '@/interface/hooks/ThemeUpdates';
|
||||
|
||||
|
||||
export const saveTheme = async (theme: LoadedCustomTheme) => {
|
||||
try {
|
||||
disableTheme();
|
||||
|
||||
console.debug('Theme to save:', theme);
|
||||
|
||||
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.debug('Theme saved successfully!');
|
||||
themeUpdates.triggerUpdate();
|
||||
} catch (error) {
|
||||
console.error('Error saving theme:', error);
|
||||
}
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import localforage from 'localforage';
|
||||
import type { CustomTheme } from '@/types/CustomThemes';
|
||||
import { applyTheme } from './applyTheme';
|
||||
import { removeTheme } from './removeTheme';
|
||||
import { settingsState } from '@/seqta/utils/listeners/SettingsState';
|
||||
|
||||
|
||||
export const setTheme = async (themeId: string) => {
|
||||
try {
|
||||
const theme = await localforage.getItem(themeId) as CustomTheme;
|
||||
|
||||
console.debug('Loading theme', theme);
|
||||
|
||||
let originalSelectedColor = { selectedColor: '' };
|
||||
|
||||
const styleElement = document.getElementById('custom-theme');
|
||||
|
||||
// Remove the currently enabled theme
|
||||
if (settingsState.selectedTheme || styleElement) {
|
||||
const currentTheme = await localforage.getItem(settingsState.selectedTheme) as CustomTheme;
|
||||
if (currentTheme) {
|
||||
await removeTheme(currentTheme);
|
||||
}
|
||||
originalSelectedColor = { selectedColor: settingsState.originalSelectedColor };
|
||||
} else {
|
||||
originalSelectedColor = { selectedColor: settingsState.selectedColor };
|
||||
}
|
||||
|
||||
await applyTheme(theme);
|
||||
|
||||
settingsState.selectedTheme = themeId
|
||||
//settingsState.selectedColor = theme.selectedColor ? theme.selectedColor : (theme.defaultColour !== '' ? theme.defaultColour : '#007bff')
|
||||
settingsState.originalSelectedColor = settingsState.selectedColor;
|
||||
settingsState.selectedColor = theme.defaultColour;
|
||||
} catch (error) {
|
||||
console.error('Error setting theme:', error);
|
||||
}
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import { getTheme } from './getTheme';
|
||||
|
||||
const saveThemeFile = (data: object, fileName: string) => {
|
||||
const fileData = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([fileData], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${fileName}.theme.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const shareTheme = async (themeID: string) => {
|
||||
try {
|
||||
// Use getTheme to retrieve the theme data
|
||||
const themeData = await getTheme(themeID);
|
||||
if (!themeData) {
|
||||
console.error('Failed to retrieve theme data');
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract images and coverImage from themeData, if they exist
|
||||
const { CustomImages = [], coverImage, ...themeWithoutImages } = themeData;
|
||||
|
||||
// Helper function to convert Blob to Base64
|
||||
const blobToBase64 = (blob: Blob) => new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const base64String = reader.result as string;
|
||||
// Extract just the base64 data without the data URL prefix
|
||||
const base64Data = base64String.split(',')[1];
|
||||
resolve(base64Data);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
// Convert cover image to Base64
|
||||
let coverImageBase64 = null;
|
||||
if (coverImage) {
|
||||
coverImageBase64 = await blobToBase64(coverImage);
|
||||
}
|
||||
|
||||
// Convert custom images to Base64
|
||||
const finalImages = await Promise.all(CustomImages.map(async (image) => {
|
||||
const imageBase64 = await blobToBase64(image.blob);
|
||||
return {
|
||||
id: image.id,
|
||||
variableName: image.variableName,
|
||||
data: imageBase64,
|
||||
};
|
||||
}));
|
||||
|
||||
// Prepare the non-file data for uploading
|
||||
const data = {
|
||||
...themeWithoutImages,
|
||||
images: finalImages.map((image) => ({
|
||||
id: image.id,
|
||||
variableName: image.variableName,
|
||||
data: image.data,
|
||||
})),
|
||||
coverImage: coverImageBase64,
|
||||
};
|
||||
|
||||
saveThemeFile(data, themeData.name || 'Unnamed_Theme');
|
||||
} catch (error) {
|
||||
console.error('Error sharing theme:', error);
|
||||
}
|
||||
};
|
||||
|
||||
export default shareTheme;
|
||||
Reference in New Issue
Block a user