From 5c23ba0381a7ed3be652a63c683ae4d66eb940a2 Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Wed, 1 Nov 2023 06:57:56 +1100 Subject: [PATCH] separate backgrounds + improve themes --- .../BackgroundSelector.css} | 0 .../src/components/BackgroundSelector.tsx | 271 ++++++++++++++ interface/src/hooks/BackgroundDataLoader.tsx | 73 ++++ interface/src/pages/Themes.tsx | 340 +----------------- .../UserInterfaceState.xcuserstate | Bin 26391 -> 28242 bytes .../xcschemes/xcschememanagement.plist | 4 +- src/SEQTA.js | 4 +- src/seqta/ui/Themes.js | 43 ++- src/seqta/utils/MessageListener.js | 8 +- 9 files changed, 393 insertions(+), 350 deletions(-) rename interface/src/{pages/Themes.css => components/BackgroundSelector.css} (100%) create mode 100644 interface/src/components/BackgroundSelector.tsx create mode 100644 interface/src/hooks/BackgroundDataLoader.tsx diff --git a/interface/src/pages/Themes.css b/interface/src/components/BackgroundSelector.css similarity index 100% rename from interface/src/pages/Themes.css rename to interface/src/components/BackgroundSelector.css diff --git a/interface/src/components/BackgroundSelector.tsx b/interface/src/components/BackgroundSelector.tsx new file mode 100644 index 00000000..7cd822a9 --- /dev/null +++ b/interface/src/components/BackgroundSelector.tsx @@ -0,0 +1,271 @@ +import { ChangeEvent, useEffect, useState } from "react"; +import { downloadPresetBackground, openDB, readAllData, writeData } from "../hooks/BackgroundDataLoader"; +import "./BackgroundSelector.css"; + +// Custom Types and Interfaces +export interface Background { + id: string; + type: string; + blob: Blob; + url?: string; + previewUrl?: string; // New field + isPreset?: boolean; + isDownloaded?: boolean; +} + +export default function BackgroundSelector() { + const [backgrounds, setBackgrounds] = useState([]); + const [selectedBackground, setSelectedBackground] = useState(localStorage.getItem('selectedBackground')); + const [downloadedPresetIds, setDownloadedPresetIds] = useState([]); + const [downloadProgress, setDownloadProgress] = useState>({}); + const [isEditMode, setIsEditMode] = useState(false); + + + const presetBackgrounds = [ + // Images + { + id: 'image-preset-1', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-1.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-1-thumb.jpg', + isPreset: true + }, + { + id: 'image-preset-2', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-2.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-2-thumb.jpg', + isPreset: true + }, + { + id: 'image-preset-3', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-3.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-3-thumb.jpg', + isPreset: true + }, + { + id: 'image-preset-4', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-4.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-4-thumb.jpg', + isPreset: true + }, + { + id: 'image-preset-5', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-5.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-5-thumb.jpg', + isPreset: true + }, + { + id: 'image-preset-6', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-6.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-6-thumb.jpg', + isPreset: true + }, + { + id: 'image-preset-7', + type: 'image', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-7.jpg', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-7-thumb.jpg', + isPreset: true + }, + + // Videos + { + id: 'video-preset-1', + type: 'video', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animated-1.mp4', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animation-1-thumb.mp4', + isPreset: true + }, + { + id: 'video-preset-2', + type: 'video', + url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animation-2.mp4', + previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animation-2-thumb.mp4', + isPreset: true + } + ]; + + const handleFileChange = async (e: ChangeEvent): Promise => { + const file = e.target.files?.[0]; + if (!file) return; + + const fileId = `${Date.now()}-${file.name}`; + const fileType = file.type.split('/')[0]; + const blob = new Blob([file], { type: file.type }); + + await writeData(fileId, fileType, blob); + setBackgrounds(prev => [...prev, { id: fileId, type: fileType, blob, url: URL.createObjectURL(blob) }]); + }; + + const loadBackgrounds = async (): Promise => { + const data = await readAllData(); + const dataWithUrls = data.map(bg => ({ ...bg, url: URL.createObjectURL(bg.blob) })); + + // Update downloaded preset IDs + setDownloadedPresetIds(data.map(bg => bg.id)); + + setBackgrounds(dataWithUrls); + }; + +const handlePresetClick = async (bg: Background): Promise => { + if (bg.isPreset) { + // Check if already exists in IndexedDB or is currently being downloaded + const existingBackgrounds = await readAllData(); + const alreadyExists = existingBackgrounds.some(ebg => ebg.id === bg.id) || downloadProgress[bg.id] !== undefined; + + if (!alreadyExists) { + setDownloadProgress(prev => ({ ...prev, [bg.id]: 0 })); + const downloadedBg = await downloadPresetBackground(bg, progress => { + console.log(`${bg}, ${progress}`); + setDownloadProgress(prev => ({ ...prev, [bg.id]: progress })); + }); + setDownloadProgress(prev => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [bg.id]: _, ...rest } = prev; + return rest; + }); + await writeData(downloadedBg.id, downloadedBg.type, downloadedBg.blob); + setBackgrounds(prev => [...prev, downloadedBg]); + setDownloadedPresetIds(prev => [...prev, downloadedBg.id]); + } + selectBackground(bg.id); + } +}; + + const selectBackground = (fileId: string): void => { + setSelectedBackground(fileId); + localStorage.setItem('selectedBackground', fileId); + }; + + const deleteBackground = async (fileId: string): Promise => { + const db = await openDB(); + const tx = db.transaction('backgrounds', 'readwrite'); + const store = tx.objectStore('backgrounds'); + store.delete(fileId); + setBackgrounds(prev => prev.filter(bg => bg.id !== fileId)); + + // Check if the background being deleted is currently selected + if (fileId === selectedBackground) { + selectNoBackground(); // Disable the current background + } + }; + + const selectNoBackground = (): void => { + setSelectedBackground(null); + localStorage.removeItem('selectedBackground'); + }; + + const calcCircumference = (radius: number) => 2 * Math.PI * radius; + + useEffect(() => { + loadBackgrounds(); + }, []); + + return ( + <> + +
+ +

Images

+
+ {/* Image uploader swatch */} +
+
+ {/* Plus icon */} +  +
+ +
+ {backgrounds.filter(bg => bg.type === 'image').map(bg => ( +
selectBackground(bg.id)} + className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${selectedBackground === bg.id ? 'dark:ring-2 ring-4' : 'ring-0'}`}> + {isEditMode && ( +
deleteBackground(bg.id)}> +
+
+ )} + swatch +
+ ))} + {backgrounds.concat(presetBackgrounds as Background[]).filter(bg => bg.type === 'image' && bg.isPreset && !bg.isDownloaded && !downloadedPresetIds.includes(bg.id)).map(bg => ( +
handlePresetClick(bg)} + className={`relative w-16 h-16 transition cursor-pointer rounded-xl duration-300 ${ isEditMode ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}> + {bg.isPreset && downloadProgress[bg.id] !== undefined && ( +
+ + + + +
+ )} +
+ + {downloadProgress[bg.id] === undefined ? '' : ''} + +
+ swatch +
+ ))} +
+ +

Videos

+
+ {/* Video uploader swatch */} +
+
+ {/* Plus icon */} +  +
+ +
+ {backgrounds.filter(bg => bg.type === 'video').map(bg => ( +
selectBackground(bg.id)} className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${selectedBackground === bg.id ? 'dark:ring-2 ring-4' : 'ring-0'}`}> + {isEditMode && ( +
deleteBackground(bg.id)}> +
+
+ )} +
+ ))} + {backgrounds.concat(presetBackgrounds as Background[]).filter(bg => bg.type === 'video' && bg.isPreset && !bg.isDownloaded && !downloadedPresetIds.includes(bg.id)).map(bg => ( +
handlePresetClick(bg)} + className={`relative w-16 h-16 transition cursor-pointer rounded-xl duration-300 ${ isEditMode ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}> + {bg.isPreset && downloadProgress[bg.id] !== undefined && ( +
+ + + + +
+ )} +
+ + {downloadProgress[bg.id] === undefined ? '' : ''} + +
+
+ ))} +
+
+ + ); +} \ No newline at end of file diff --git a/interface/src/hooks/BackgroundDataLoader.tsx b/interface/src/hooks/BackgroundDataLoader.tsx new file mode 100644 index 00000000..dc26b266 --- /dev/null +++ b/interface/src/hooks/BackgroundDataLoader.tsx @@ -0,0 +1,73 @@ +import { Background } from "../components/BackgroundSelector"; + +export const downloadPresetBackground = async (background: Background, onProgress: (progress: number) => void): Promise => { + const response = await fetch(background.url as string); + + const totalLength = +response.headers.get('Content-Length')!; + let receivedLength = 0; + + const reader = response.body?.getReader(); + const chunks = []; + + // eslint-disable-next-line no-constant-condition + while (true) { + const { done, value } = await reader!.read(); + + if (done) break; + + chunks.push(value!); + receivedLength += value!.length; + + onProgress(Math.ceil(receivedLength / totalLength * 100)); + } + + const blob = new Blob(chunks); + await writeData(background.id, background.type, blob); + + return { + id: background.id, + type: background.type, + blob, + url: URL.createObjectURL(blob), + }; +}; +// IndexedDB utility functions +export const openDB = () => { + return new Promise((resolve, reject) => { + const request = indexedDB.open('MyDatabase', 1); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + db.createObjectStore('backgrounds', { keyPath: 'id' }); + }; + }); +}; +export const writeData = async (fileId: string, type: string, blob: Blob) => { + return new Promise((resolve, reject) => { + openDB().then(async (db) => { + const tx = db.transaction('backgrounds', 'readwrite'); + const store = tx.objectStore('backgrounds'); + const request = store.put({ id: fileId, type, blob }); + + await new Promise((res, rej) => { + tx.oncomplete = () => res(request.result); + tx.onerror = () => rej(tx.error); + }).then(resolve, reject); + + }).catch(reject); + }); +}; +export const readAllData = async (): Promise => { + const db = await openDB(); + const tx = db.transaction('backgrounds', 'readonly'); + const store = tx.objectStore('backgrounds'); + const request = store.getAll(); + + return await new Promise((resolve, reject) => { + request.onsuccess = () => resolve(request.result); + request.onerror = () => reject(request.error); + }); +}; diff --git a/interface/src/pages/Themes.tsx b/interface/src/pages/Themes.tsx index 2e2e5859..020e4a47 100644 --- a/interface/src/pages/Themes.tsx +++ b/interface/src/pages/Themes.tsx @@ -1,345 +1,11 @@ -import { useState, useEffect, ChangeEvent, FC } from 'react'; -import './Themes.css'; - -// Custom Types and Interfaces -interface Background { - id: string; - type: string; - blob: Blob; - url?: string; - previewUrl?: string; // New field - isPreset?: boolean; - isDownloaded?: boolean; -} - -const downloadPresetBackground = async (background: Background, onProgress: (progress: number) => void): Promise => { - const response = await fetch(background.url as string); - - const totalLength = +response.headers.get('Content-Length')!; - let receivedLength = 0; - - const reader = response.body?.getReader(); - const chunks = []; - - // eslint-disable-next-line no-constant-condition - while (true) { - const { done, value } = await reader!.read(); - - if (done) break; - - chunks.push(value!); - receivedLength += value!.length; - - onProgress(Math.ceil(receivedLength / totalLength * 100)); - } - - const blob = new Blob(chunks); - await writeData(background.id, background.type, blob); - - return { - id: background.id, - type: background.type, - blob, - url: URL.createObjectURL(blob), - }; -}; - -// IndexedDB utility functions -const openDB = () => { - return new Promise((resolve, reject) => { - const request = indexedDB.open('MyDatabase', 1); - - request.onerror = () => reject(request.error); - request.onsuccess = () => resolve(request.result); - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result; - db.createObjectStore('backgrounds', { keyPath: 'id' }); - }; - }); -}; - -const writeData = async (fileId: string, type: string, blob: Blob) => { - return new Promise((resolve, reject) => { - openDB().then(async db => { - const tx = db.transaction('backgrounds', 'readwrite'); - const store = tx.objectStore('backgrounds'); - const request = store.put({ id: fileId, type, blob }); - - await new Promise((res, rej) => { - tx.oncomplete = () => res(request.result); - tx.onerror = () => rej(tx.error); - }).then(resolve, reject); - - }).catch(reject); - }); -}; - -const readAllData = async (): Promise => { - const db = await openDB(); - const tx = db.transaction('backgrounds', 'readonly'); - const store = tx.objectStore('backgrounds'); - const request = store.getAll(); - - return await new Promise((resolve, reject) => { - request.onsuccess = () => resolve(request.result); - request.onerror = () => reject(request.error); - }); -}; +import { FC } from 'react'; +import BackgroundSelector from '../components/BackgroundSelector'; const Themes: FC = () => { - const [backgrounds, setBackgrounds] = useState([]); - const [selectedBackground, setSelectedBackground] = useState(localStorage.getItem('selectedBackground')); - const [downloadedPresetIds, setDownloadedPresetIds] = useState([]); - const [downloadProgress, setDownloadProgress] = useState>({}); - const [isEditMode, setIsEditMode] = useState(false); - - - const presetBackgrounds = [ - // Images - { - id: 'image-preset-1', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-1.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-1-thumb.jpg', - isPreset: true - }, - { - id: 'image-preset-2', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-2.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-2-thumb.jpg', - isPreset: true - }, - { - id: 'image-preset-3', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-3.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-3-thumb.jpg', - isPreset: true - }, - { - id: 'image-preset-4', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-4.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-4-thumb.jpg', - isPreset: true - }, - { - id: 'image-preset-5', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-5.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-5-thumb.jpg', - isPreset: true - }, - { - id: 'image-preset-6', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-6.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-6-thumb.jpg', - isPreset: true - }, - { - id: 'image-preset-7', - type: 'image', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-7.jpg', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/images/background-7-thumb.jpg', - isPreset: true - }, - - // Videos - { - id: 'video-preset-1', - type: 'video', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animated-1.mp4', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animation-1-thumb.mp4', - isPreset: true - }, - { - id: 'video-preset-2', - type: 'video', - url: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animation-2.mp4', - previewUrl: 'https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/backgrounds/videos/animation-2-thumb.mp4', - isPreset: true - } - ]; - - const handleFileChange = async (e: ChangeEvent): Promise => { - const file = e.target.files?.[0]; - if (!file) return; - - const fileId = `${Date.now()}-${file.name}`; - const fileType = file.type.split('/')[0]; - const blob = new Blob([file], { type: file.type }); - - await writeData(fileId, fileType, blob); - setBackgrounds(prev => [...prev, { id: fileId, type: fileType, blob, url: URL.createObjectURL(blob) }]); - }; - - const loadBackgrounds = async (): Promise => { - const data = await readAllData(); - const dataWithUrls = data.map(bg => ({ ...bg, url: URL.createObjectURL(bg.blob) })); - - // Update downloaded preset IDs - setDownloadedPresetIds(data.map(bg => bg.id)); - - setBackgrounds(dataWithUrls); - }; - -const handlePresetClick = async (bg: Background): Promise => { - if (bg.isPreset) { - // Check if already exists in IndexedDB or is currently being downloaded - const existingBackgrounds = await readAllData(); - const alreadyExists = existingBackgrounds.some(ebg => ebg.id === bg.id) || downloadProgress[bg.id] !== undefined; - - if (!alreadyExists) { - setDownloadProgress(prev => ({ ...prev, [bg.id]: 0 })); - const downloadedBg = await downloadPresetBackground(bg, progress => { - console.log(`${bg}, ${progress}`); - setDownloadProgress(prev => ({ ...prev, [bg.id]: progress })); - }); - setDownloadProgress(prev => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { [bg.id]: _, ...rest } = prev; - return rest; - }); - await writeData(downloadedBg.id, downloadedBg.type, downloadedBg.blob); - setBackgrounds(prev => [...prev, downloadedBg]); - setDownloadedPresetIds(prev => [...prev, downloadedBg.id]); - } - selectBackground(bg.id); - } -}; - - const selectBackground = (fileId: string): void => { - setSelectedBackground(fileId); - localStorage.setItem('selectedBackground', fileId); - }; - - const deleteBackground = async (fileId: string): Promise => { - const db = await openDB(); - const tx = db.transaction('backgrounds', 'readwrite'); - const store = tx.objectStore('backgrounds'); - store.delete(fileId); - setBackgrounds(prev => prev.filter(bg => bg.id !== fileId)); - - // Check if the background being deleted is currently selected - if (fileId === selectedBackground) { - selectNoBackground(); // Disable the current background - } - }; - - const selectNoBackground = (): void => { - setSelectedBackground(null); - localStorage.removeItem('selectedBackground'); - }; - - const calcCircumference = (radius: number) => 2 * Math.PI * radius; - - useEffect(() => { - loadBackgrounds(); - }, []); return (
- -
- -

Images

-
- {/* Image uploader swatch */} -
-
- {/* Plus icon */} -  -
- -
- {backgrounds.filter(bg => bg.type === 'image').map(bg => ( -
selectBackground(bg.id)} - className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${selectedBackground === bg.id ? 'dark:ring-2 ring-4' : 'ring-0'}`}> - {isEditMode && ( -
deleteBackground(bg.id)}> -
-
- )} - swatch -
- ))} - {backgrounds.concat(presetBackgrounds as Background[]).filter(bg => bg.type === 'image' && bg.isPreset && !bg.isDownloaded && !downloadedPresetIds.includes(bg.id)).map(bg => ( -
handlePresetClick(bg)} - className={`relative w-16 h-16 transition cursor-pointer rounded-xl duration-300 ${ isEditMode ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}> - {bg.isPreset && downloadProgress[bg.id] !== undefined && ( -
- - - - -
- )} -
- - {downloadProgress[bg.id] === undefined ? '' : ''} - -
- swatch -
- ))} -
- -

Videos

-
- {/* Video uploader swatch */} -
-
- {/* Plus icon */} -  -
- -
- {backgrounds.filter(bg => bg.type === 'video').map(bg => ( -
selectBackground(bg.id)} className={`relative w-16 h-16 cursor-pointer rounded-xl transition ring dark:ring-white ring-zinc-300 ${isEditMode ? 'animate-shake' : ''} ${selectedBackground === bg.id ? 'dark:ring-2 ring-4' : 'ring-0'}`}> - {isEditMode && ( -
deleteBackground(bg.id)}> -
-
- )} -
- ))} - {backgrounds.concat(presetBackgrounds as Background[]).filter(bg => bg.type === 'video' && bg.isPreset && !bg.isDownloaded && !downloadedPresetIds.includes(bg.id)).map(bg => ( -
handlePresetClick(bg)} - className={`relative w-16 h-16 transition cursor-pointer rounded-xl duration-300 ${ isEditMode ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}> - {bg.isPreset && downloadProgress[bg.id] !== undefined && ( -
- - - - -
- )} -
- - {downloadProgress[bg.id] === undefined ? '' : ''} - -
-
- ))} -
-
+
); }; diff --git a/safari/BetterSEQTA+.xcodeproj/project.xcworkspace/xcuserdata/sethburkart.xcuserdatad/UserInterfaceState.xcuserstate b/safari/BetterSEQTA+.xcodeproj/project.xcworkspace/xcuserdata/sethburkart.xcuserdatad/UserInterfaceState.xcuserstate index 1a9e8eb352833e15268cec48336e68f7aa1f5795..da751e9b375041c9ab57faa60c4ac9e97e3bee9d 100644 GIT binary patch delta 14498 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 diff --git a/safari/BetterSEQTA+.xcodeproj/xcuserdata/sethburkart.xcuserdatad/xcschemes/xcschememanagement.plist b/safari/BetterSEQTA+.xcodeproj/xcuserdata/sethburkart.xcuserdatad/xcschemes/xcschememanagement.plist index 4865fbf4..cc3d30cf 100644 --- a/safari/BetterSEQTA+.xcodeproj/xcuserdata/sethburkart.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/safari/BetterSEQTA+.xcodeproj/xcuserdata/sethburkart.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ BetterSEQTA+ (iOS).xcscheme_^#shared#^_ orderHint - 1 + 0 BetterSEQTA+ (macOS).xcscheme_^#shared#^_ orderHint - 0 + 1 diff --git a/src/SEQTA.js b/src/SEQTA.js index f544b1ba..cc5a4313 100644 --- a/src/SEQTA.js +++ b/src/SEQTA.js @@ -15,7 +15,7 @@ import { MessageHandler } from "./seqta/utils/MessageListener.js"; import { updateBgDurations } from "./seqta/ui/Animation.js"; import { updateAllColors } from "./seqta/ui/colors/Manager.js"; import { appendBackgroundToUI } from "./seqta/ui/ImageBackgrounds.js"; -import { EnableThemes } from "./seqta/ui/Themes.js"; +import { enableCurrentTheme } from "./seqta/ui/Themes.js"; export let isChrome = window.chrome; let SettingsClicked = false; @@ -774,7 +774,7 @@ document.addEventListener( chrome.storage.local.get(null, function (items) { main(items); - EnableThemes(); + enableCurrentTheme(); }); } if ( diff --git a/src/seqta/ui/Themes.js b/src/seqta/ui/Themes.js index d88985fa..3169b698 100644 --- a/src/seqta/ui/Themes.js +++ b/src/seqta/ui/Themes.js @@ -45,10 +45,6 @@ const saveToIndexedDB = async (theme, themeName) => { // Apply theme from storage via localForage to document const applyTheme = async (themeName) => { const { css, className, images } = await localforage.getItem(`css_${themeName}`); - console.log(`Applying theme ${themeName}`); - console.log(`CSS: ${css}`); - console.log(`className: ${className}`); - console.log(`images: ${images}`); // Apply CSS const style = document.createElement("style"); @@ -56,7 +52,7 @@ const applyTheme = async (themeName) => { document.head.appendChild(style); // Apply className - document.body.classList.add(className); + if (className) document.body.classList.add(className); // Apply images if (images) { @@ -70,8 +66,41 @@ const applyTheme = async (themeName) => { } }; +export const downloadTheme = async (themeName, themeUrl) => { + console.log(`Fetching theme ${themeName} from ${themeUrl}...`); + const themeData = await fetchThemeJSON(themeUrl); + await saveToIndexedDB(themeData, themeName); + console.log(`Theme ${themeName} saved to IndexedDB`); + setTheme(themeName); + return; +}; + +export const setTheme = async (themeName, themeUrl) => { + if (!(await themeExistsInDB(themeName))) { + await downloadTheme(themeName, themeUrl); + } + + localStorage.setItem("selectedTheme", themeName); + await applyTheme(themeName).catch((error) => { + console.error(`Failed to apply theme: ${error}`); + }); +}; + +export const enableCurrentTheme = async () => { + const currentTheme = localStorage.getItem("selectedTheme"); + + if (currentTheme) { + console.log(`Enabling current theme: ${currentTheme}`); + await applyTheme(currentTheme).catch((error) => { + console.error(`Failed to apply current theme: ${error}`); + }); + } else { + console.log("No current theme set in localStorage."); + } +}; + // 🚀 Main function to orchestrate everything 🚀 -export const EnableThemes = async () => { +/* export const EnableThemes = async () => { const availableThemes = [ { name: "dark", url: "https://raw.githubusercontent.com/SethBurkart123/BetterSEQTA-Themes/main/themes/test.json" } ]; @@ -95,4 +124,4 @@ export const EnableThemes = async () => { await applyTheme(themeToApply).catch((error) => { console.error(`Failed to apply theme: ${error}`); }); -}; \ No newline at end of file +}; */ \ No newline at end of file diff --git a/src/seqta/utils/MessageListener.js b/src/seqta/utils/MessageListener.js index d39a3d61..3952a1a7 100644 --- a/src/seqta/utils/MessageListener.js +++ b/src/seqta/utils/MessageListener.js @@ -1,6 +1,7 @@ /* global chrome */ import { MenuOptionsOpen, OpenMenuOptions, closeSettings } from "../../SEQTA.js"; +import { downloadTheme, setTheme } from "../ui/Themes.js"; export class MessageHandler { constructor() { @@ -13,8 +14,11 @@ export class MessageHandler { case "EditSidebar": this.editSidebar(); break; - case "Theme": - console.log("Theme message received"); + case "SetTheme": + setTheme(request.body.themeName, request.body.themeURL); + break; + case "DownloadTheme": + downloadTheme(request.body.themeURL, request.body.themeName); break; default: