From 0abd8dadab00eb9e51d7c47adc44ae23187de4ed Mon Sep 17 00:00:00 2001 From: SethBurkart123 Date: Thu, 4 Apr 2024 10:16:02 +1100 Subject: [PATCH] add editing functionality --- src/interface/components/CodeEditor.tsx | 14 +++- src/interface/components/ThemeCover.tsx | 10 +++ src/interface/pages/ThemeCreator.tsx | 71 ++++++++++++++++---- src/seqta/ui/ThemeCreator.ts | 5 +- src/seqta/ui/Themes.ts | 45 +++++++++++-- src/seqta/utils/listeners/MessageListener.ts | 20 +++--- 6 files changed, 132 insertions(+), 33 deletions(-) diff --git a/src/interface/components/CodeEditor.tsx b/src/interface/components/CodeEditor.tsx index 43250d60..9bb510cd 100644 --- a/src/interface/components/CodeEditor.tsx +++ b/src/interface/components/CodeEditor.tsx @@ -5,8 +5,17 @@ import { less } from '@codemirror/lang-less' import { useCallback, useEffect, useState } from 'react'; import './CodeEditor.css' -export default function CodeEditor({ callback, initialState, height, className }: { callback: (value: string) => void, initialState: string, height: string, className?: string}) { - const [value, setValue] = useState(initialState) +export default function CodeEditor({ + className = '', + height = '100%', + value, + setValue +}: { + className?: string; + height?: string; + value: string; + setValue: (value: string) => void; +}) { const [darkMode, setDarkMode] = useState(false) useEffect(() => { @@ -17,7 +26,6 @@ export default function CodeEditor({ callback, initialState, height, className } const onChange = useCallback((value: string, _: ViewUpdate) => { setValue(value) - callback(value) }, []) return( diff --git a/src/interface/components/ThemeCover.tsx b/src/interface/components/ThemeCover.tsx index b3dfc79a..8c7400e7 100644 --- a/src/interface/components/ThemeCover.tsx +++ b/src/interface/components/ThemeCover.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { CustomTheme } from '../types/CustomThemes'; +import browser from 'webextension-polyfill'; +import { PencilIcon } from '@heroicons/react/24/outline'; type ThemeCoverProps = { theme: Omit; @@ -40,6 +42,14 @@ export const ThemeCover: React.FC = ({
)} + {/* edit button */} +
browser.runtime.sendMessage({ type: 'currentTab', info: 'OpenThemeCreator', body: { themeID: theme.id } })} + > + +
+
{/* Render theme cover image or placeholder */} {/* {theme.CustomImages.length > 0 ? ( diff --git a/src/interface/pages/ThemeCreator.tsx b/src/interface/pages/ThemeCreator.tsx index cf504d1f..c620df9c 100644 --- a/src/interface/pages/ThemeCreator.tsx +++ b/src/interface/pages/ThemeCreator.tsx @@ -5,11 +5,11 @@ import Accordion from '../components/Accordian'; import Switch from '../components/Switch'; import { sendThemeUpdate } from '../hooks/ThemeManagment'; import { PlusIcon, XMarkIcon } from '@heroicons/react/24/outline'; -import localforage from 'localforage'; import { v4 as uuidv4 } from 'uuid'; -import { CustomTheme } from '../types/CustomThemes'; +import { CustomTheme, CustomThemeBase64 } from '../types/CustomThemes'; +import browser from 'webextension-polyfill'; -function ThemeCreator({ themeID }: { themeID?: string }) { +function ThemeCreator() { const [theme, setTheme] = useState({ id: uuidv4(), name: '', @@ -17,18 +17,65 @@ function ThemeCreator({ themeID }: { themeID?: string }) { defaultColour: '', CanChangeColour: true, CustomCSS: '', - CustomImages: [] + CustomImages: [], }); useEffect(() => { - if (themeID) { - localforage.getItem(themeID).then((theme) => { - if (theme) { - setTheme(theme as CustomTheme); + const getTheme = async (themeID: string) => { + const theme = await browser.runtime.sendMessage({ + type: 'currentTab', + info: 'GetTheme', + body: { + themeID: themeID, } - }); + }) as CustomThemeBase64 | undefined; + + if (theme) { + // base64toblob to convert it to a blob url + const CustomImages = theme.CustomImages.map((image) => { + const base64Index = image.url.indexOf(',') + 1; + const imageBase64 = image.url.substring(base64Index); + + // Convert base64 to blob + const byteCharacters = atob(imageBase64); + const byteNumbers = new Uint8Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { type: 'image/png' }); + + return { + id: image.id, + blob: blob, + variableName: image.variableName, + }; + }); + + setTheme({ + ...theme, + CustomImages, + }); + + sendThemeUpdate({ + ...theme, + CustomImages: CustomImages, + }, false, true); + } + + }; + + // get ThemeID from URL params + const urlParams = new URLSearchParams(window.location.search); + const themeID = urlParams.get('themeID'); + + console.log('ThemeID:', themeID); + + if (themeID) { + getTheme(themeID); } - }); + + }, []); const generateImageId = () => { return '_' + Math.random().toString(36).substr(2, 9); @@ -173,8 +220,8 @@ function ThemeCreator({ themeID }: { themeID?: string }) { + value={theme.CustomCSS} + setValue={CodeUpdate} /> diff --git a/src/seqta/ui/ThemeCreator.ts b/src/seqta/ui/ThemeCreator.ts index 3dc45f90..7d0507d0 100644 --- a/src/seqta/ui/ThemeCreator.ts +++ b/src/seqta/ui/ThemeCreator.ts @@ -2,15 +2,16 @@ import browser from "webextension-polyfill"; /** * Open the Theme Creator sidebar, it is an embedded page loaded similar to the extension popup + * @param themeID - The ID of the theme to load in the Theme Creator * @returns void */ -export function OpenThemeCreator() { +export function OpenThemeCreator( themeID: string = '' ) { CloseThemeCreator(); const width = '310px'; const themeCreatorIframe: HTMLIFrameElement = document.createElement('iframe'); - themeCreatorIframe.src = `${browser.runtime.getURL('src/interface/index.html')}#themeCreator`; + themeCreatorIframe.src = `${browser.runtime.getURL('src/interface/index.html')}${ themeID != '' ? `?themeID=${themeID}` : '' }#themeCreator`; themeCreatorIframe.id = 'themeCreatorIframe'; themeCreatorIframe.setAttribute('allowTransparency', 'true'); themeCreatorIframe.setAttribute('excludeDarkCheck', 'true'); diff --git a/src/seqta/ui/Themes.ts b/src/seqta/ui/Themes.ts index 1c06e1c4..50f101ac 100644 --- a/src/seqta/ui/Themes.ts +++ b/src/seqta/ui/Themes.ts @@ -152,7 +152,7 @@ export const UpdateImageData = (imageData2: { id: string; base64: string }) => { const { id, base64 } = imageData2; if (imageData[id]) { - imageData[id].url = updateImage({ id, url: base64, variableName: imageData[id].variableName }); + imageData[id].url = base64toblob(base64); const { variableName } = imageData[id]; document.documentElement.style.setProperty('--' + variableName, `url(${imageData[id].url})`); } @@ -172,10 +172,10 @@ function removeImageFromDocument(variableName: string) { document.documentElement.style.removeProperty('--' + variableName); } -export function updateImage(image: CustomImageBase64) { +export function base64toblob(base64: string) { // Extract base64 data from the data URI - const base64Index = image.url.indexOf(',') + 1; - const imageBase64 = image.url.substring(base64Index); + const base64Index = base64.indexOf(',') + 1; + const imageBase64 = base64.substring(base64Index); // Convert base64 to blob const byteCharacters = atob(imageBase64); @@ -233,6 +233,43 @@ const removeTheme = (theme: CustomTheme) => { }); }; +const blobToBase64 = (blob: Blob) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const base64 = reader.result as string; + resolve(base64); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); +}; + +export const getTheme = async (themeId: string): Promise => { + try { + const theme = await localforage.getItem(themeId) as CustomTheme; + + const CustomImages: CustomImageBase64[] = await Promise.all( + theme.CustomImages.map(async (image) => { + const base64 = await blobToBase64(image.blob); + return { + id: image.id, + variableName: image.variableName, + url: base64, + }; + }) + ); + + return { + ...theme, + CustomImages, + }; + } catch (error) { + console.error('Error getting theme:', error); + return null; + } +} + export const setTheme = async (themeId: string) => { try { const enabledTheme = await browser.storage.local.get('selectedTheme') as { selectedTheme: string }; diff --git a/src/seqta/utils/listeners/MessageListener.ts b/src/seqta/utils/listeners/MessageListener.ts index 0170d8e6..24a5aec6 100644 --- a/src/seqta/utils/listeners/MessageListener.ts +++ b/src/seqta/utils/listeners/MessageListener.ts @@ -1,7 +1,7 @@ import browser from 'webextension-polyfill' import { MenuOptionsOpen, OpenMenuOptions, OpenWhatsNewPopup, closeSettings } from '../../../SEQTA'; -import { UpdateImageData, UpdateThemePreview, deleteTheme, disableTheme, getAvailableThemes, saveTheme, setTheme } from '../../ui/Themes'; +import { UpdateImageData, UpdateThemePreview, deleteTheme, disableTheme, getAvailableThemes, getTheme, saveTheme, setTheme } from '../../ui/Themes'; import { CloseThemeCreator, OpenThemeCreator } from '../../ui/ThemeCreator'; export class MessageHandler { @@ -28,17 +28,12 @@ export class MessageHandler { sendResponse({ status: 'success' }); } break; - - case 'UpdateThemeImageData': - UpdateImageData(request.body); - sendResponse({ status: 'success' }); - break; - - case 'SaveTheme': - saveTheme(request.body).then(() => { - sendResponse({ status: 'success' }); + + case 'GetTheme': + getTheme(request.body.themeID).then((theme) => { + sendResponse(theme); }); - break; + return true; case 'SetTheme': setTheme(request.body.themeID).then(() => { @@ -71,7 +66,8 @@ export class MessageHandler { break; case 'OpenThemeCreator': - OpenThemeCreator(); + const themeID = request?.body?.themeID; + OpenThemeCreator( themeID ? themeID : '' ); closeSettings(); sendResponse({ status: 'success' }); break;