mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 11:44:40 +00:00
add editing functionality
This commit is contained in:
@@ -5,8 +5,17 @@ import { less } from '@codemirror/lang-less'
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import './CodeEditor.css'
|
import './CodeEditor.css'
|
||||||
|
|
||||||
export default function CodeEditor({ callback, initialState, height, className }: { callback: (value: string) => void, initialState: string, height: string, className?: string}) {
|
export default function CodeEditor({
|
||||||
const [value, setValue] = useState(initialState)
|
className = '',
|
||||||
|
height = '100%',
|
||||||
|
value,
|
||||||
|
setValue
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
height?: string;
|
||||||
|
value: string;
|
||||||
|
setValue: (value: string) => void;
|
||||||
|
}) {
|
||||||
const [darkMode, setDarkMode] = useState(false)
|
const [darkMode, setDarkMode] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -17,7 +26,6 @@ export default function CodeEditor({ callback, initialState, height, className }
|
|||||||
|
|
||||||
const onChange = useCallback((value: string, _: ViewUpdate) => {
|
const onChange = useCallback((value: string, _: ViewUpdate) => {
|
||||||
setValue(value)
|
setValue(value)
|
||||||
callback(value)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return(
|
return(
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CustomTheme } from '../types/CustomThemes';
|
import { CustomTheme } from '../types/CustomThemes';
|
||||||
|
import browser from 'webextension-polyfill';
|
||||||
|
import { PencilIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
type ThemeCoverProps = {
|
type ThemeCoverProps = {
|
||||||
theme: Omit<CustomTheme, 'CustomImages'>;
|
theme: Omit<CustomTheme, 'CustomImages'>;
|
||||||
@@ -40,6 +42,14 @@ export const ThemeCover: React.FC<ThemeCoverProps> = ({
|
|||||||
<div className="w-4 h-0.5 bg-white"></div>
|
<div className="w-4 h-0.5 bg-white"></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* edit button */}
|
||||||
|
<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-blue-600 rounded-full place-items-center"
|
||||||
|
onClick={() => browser.runtime.sendMessage({ type: 'currentTab', info: 'OpenThemeCreator', body: { themeID: theme.id } })}
|
||||||
|
>
|
||||||
|
<PencilIcon className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="relative top-0 z-10 flex justify-center w-full h-full overflow-hidden transition dark:text-white rounded-xl group place-items-center bg-zinc-100 dark:bg-zinc-900">
|
<div className="relative top-0 z-10 flex justify-center w-full h-full overflow-hidden transition dark:text-white rounded-xl group place-items-center bg-zinc-100 dark:bg-zinc-900">
|
||||||
{/* Render theme cover image or placeholder */}
|
{/* Render theme cover image or placeholder */}
|
||||||
{/* {theme.CustomImages.length > 0 ? (
|
{/* {theme.CustomImages.length > 0 ? (
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import Accordion from '../components/Accordian';
|
|||||||
import Switch from '../components/Switch';
|
import Switch from '../components/Switch';
|
||||||
import { sendThemeUpdate } from '../hooks/ThemeManagment';
|
import { sendThemeUpdate } from '../hooks/ThemeManagment';
|
||||||
import { PlusIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
import { PlusIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||||
import localforage from 'localforage';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
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<CustomTheme>({
|
const [theme, setTheme] = useState<CustomTheme>({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
name: '',
|
name: '',
|
||||||
@@ -17,18 +17,65 @@ function ThemeCreator({ themeID }: { themeID?: string }) {
|
|||||||
defaultColour: '',
|
defaultColour: '',
|
||||||
CanChangeColour: true,
|
CanChangeColour: true,
|
||||||
CustomCSS: '',
|
CustomCSS: '',
|
||||||
CustomImages: []
|
CustomImages: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (themeID) {
|
const getTheme = async (themeID: string) => {
|
||||||
localforage.getItem(themeID).then((theme) => {
|
const theme = await browser.runtime.sendMessage({
|
||||||
if (theme) {
|
type: 'currentTab',
|
||||||
setTheme(theme as CustomTheme);
|
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 = () => {
|
const generateImageId = () => {
|
||||||
return '_' + Math.random().toString(36).substr(2, 9);
|
return '_' + Math.random().toString(36).substr(2, 9);
|
||||||
@@ -173,8 +220,8 @@ function ThemeCreator({ themeID }: { themeID?: string }) {
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
className='mt-2'
|
className='mt-2'
|
||||||
height='300px'
|
height='300px'
|
||||||
initialState={theme.CustomCSS}
|
value={theme.CustomCSS}
|
||||||
callback={CodeUpdate} />
|
setValue={CodeUpdate} />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -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
|
* 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
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export function OpenThemeCreator() {
|
export function OpenThemeCreator( themeID: string = '' ) {
|
||||||
CloseThemeCreator();
|
CloseThemeCreator();
|
||||||
|
|
||||||
const width = '310px';
|
const width = '310px';
|
||||||
|
|
||||||
const themeCreatorIframe: HTMLIFrameElement = document.createElement('iframe');
|
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.id = 'themeCreatorIframe';
|
||||||
themeCreatorIframe.setAttribute('allowTransparency', 'true');
|
themeCreatorIframe.setAttribute('allowTransparency', 'true');
|
||||||
themeCreatorIframe.setAttribute('excludeDarkCheck', 'true');
|
themeCreatorIframe.setAttribute('excludeDarkCheck', 'true');
|
||||||
|
|||||||
+41
-4
@@ -152,7 +152,7 @@ export const UpdateImageData = (imageData2: { id: string; base64: string }) => {
|
|||||||
const { id, base64 } = imageData2;
|
const { id, base64 } = imageData2;
|
||||||
|
|
||||||
if (imageData[id]) {
|
if (imageData[id]) {
|
||||||
imageData[id].url = updateImage({ id, url: base64, variableName: imageData[id].variableName });
|
imageData[id].url = base64toblob(base64);
|
||||||
const { variableName } = imageData[id];
|
const { variableName } = imageData[id];
|
||||||
document.documentElement.style.setProperty('--' + variableName, `url(${imageData[id].url})`);
|
document.documentElement.style.setProperty('--' + variableName, `url(${imageData[id].url})`);
|
||||||
}
|
}
|
||||||
@@ -172,10 +172,10 @@ function removeImageFromDocument(variableName: string) {
|
|||||||
document.documentElement.style.removeProperty('--' + variableName);
|
document.documentElement.style.removeProperty('--' + variableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateImage(image: CustomImageBase64) {
|
export function base64toblob(base64: string) {
|
||||||
// Extract base64 data from the data URI
|
// Extract base64 data from the data URI
|
||||||
const base64Index = image.url.indexOf(',') + 1;
|
const base64Index = base64.indexOf(',') + 1;
|
||||||
const imageBase64 = image.url.substring(base64Index);
|
const imageBase64 = base64.substring(base64Index);
|
||||||
|
|
||||||
// Convert base64 to blob
|
// Convert base64 to blob
|
||||||
const byteCharacters = atob(imageBase64);
|
const byteCharacters = atob(imageBase64);
|
||||||
@@ -233,6 +233,43 @@ const removeTheme = (theme: CustomTheme) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const blobToBase64 = (blob: Blob) => {
|
||||||
|
return new Promise<string>((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<CustomThemeBase64 | null> => {
|
||||||
|
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) => {
|
export const setTheme = async (themeId: string) => {
|
||||||
try {
|
try {
|
||||||
const enabledTheme = await browser.storage.local.get('selectedTheme') as { selectedTheme: string };
|
const enabledTheme = await browser.storage.local.get('selectedTheme') as { selectedTheme: string };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import browser from 'webextension-polyfill'
|
||||||
|
|
||||||
import { MenuOptionsOpen, OpenMenuOptions, OpenWhatsNewPopup, closeSettings } from '../../../SEQTA';
|
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';
|
import { CloseThemeCreator, OpenThemeCreator } from '../../ui/ThemeCreator';
|
||||||
|
|
||||||
export class MessageHandler {
|
export class MessageHandler {
|
||||||
@@ -28,17 +28,12 @@ export class MessageHandler {
|
|||||||
sendResponse({ status: 'success' });
|
sendResponse({ status: 'success' });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'UpdateThemeImageData':
|
case 'GetTheme':
|
||||||
UpdateImageData(request.body);
|
getTheme(request.body.themeID).then((theme) => {
|
||||||
sendResponse({ status: 'success' });
|
sendResponse(theme);
|
||||||
break;
|
|
||||||
|
|
||||||
case 'SaveTheme':
|
|
||||||
saveTheme(request.body).then(() => {
|
|
||||||
sendResponse({ status: 'success' });
|
|
||||||
});
|
});
|
||||||
break;
|
return true;
|
||||||
|
|
||||||
case 'SetTheme':
|
case 'SetTheme':
|
||||||
setTheme(request.body.themeID).then(() => {
|
setTheme(request.body.themeID).then(() => {
|
||||||
@@ -71,7 +66,8 @@ export class MessageHandler {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'OpenThemeCreator':
|
case 'OpenThemeCreator':
|
||||||
OpenThemeCreator();
|
const themeID = request?.body?.themeID;
|
||||||
|
OpenThemeCreator( themeID ? themeID : '' );
|
||||||
closeSettings();
|
closeSettings();
|
||||||
sendResponse({ status: 'success' });
|
sendResponse({ status: 'success' });
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user