mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
add theme transfer
This commit is contained in:
@@ -1,18 +1,21 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { CustomTheme } from '../types/CustomThemes';
|
import { CustomTheme, DownloadedTheme } from '../types/CustomThemes';
|
||||||
import browser from 'webextension-polyfill';
|
import browser from 'webextension-polyfill';
|
||||||
import { ArrowUpOnSquareIcon, PencilIcon } from '@heroicons/react/24/outline';
|
import { ArrowUpOnSquareIcon, PencilIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { sendThemeUpdate } from '../hooks/ThemeManagment';
|
||||||
|
|
||||||
type ThemeCoverProps = {
|
type ThemeCoverProps = {
|
||||||
theme: Omit<CustomTheme, 'CustomImages'>;
|
theme: Omit<CustomTheme, 'CustomImages'> | DownloadedTheme;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
isEditMode: boolean;
|
isEditMode: boolean;
|
||||||
|
downloaded?: boolean;
|
||||||
onThemeSelect: (themeId: string) => void;
|
onThemeSelect: (themeId: string) => void;
|
||||||
onThemeDelete: (themeId: string) => void;
|
onThemeDelete: (themeId: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ThemeCover: React.FC<ThemeCoverProps> = ({
|
export const ThemeCover: React.FC<ThemeCoverProps> = ({
|
||||||
theme,
|
theme,
|
||||||
|
downloaded,
|
||||||
isSelected,
|
isSelected,
|
||||||
isEditMode,
|
isEditMode,
|
||||||
onThemeSelect,
|
onThemeSelect,
|
||||||
@@ -21,7 +24,12 @@ export const ThemeCover: React.FC<ThemeCoverProps> = ({
|
|||||||
const [uploading, setUploading] = useState<boolean>(false);
|
const [uploading, setUploading] = useState<boolean>(false);
|
||||||
const handleThemeClick = () => {
|
const handleThemeClick = () => {
|
||||||
if (isEditMode) return;
|
if (isEditMode) return;
|
||||||
|
if (downloaded) {
|
||||||
|
sendThemeUpdate(theme as DownloadedTheme, true)
|
||||||
|
} else {
|
||||||
|
console.log(theme)
|
||||||
onThemeSelect(theme.id);
|
onThemeSelect(theme.id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteClick = (e: React.MouseEvent) => {
|
const handleDeleteClick = (e: React.MouseEvent) => {
|
||||||
@@ -54,7 +62,7 @@ export const ThemeCover: React.FC<ThemeCoverProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ isEditMode ? <></> :
|
{ !isEditMode || !downloaded &&
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className="absolute z-20 flex w-8 h-8 p-2 text-white transition-all rounded-full delay-[20ms] opacity-0 top-1 right-2 dark:bg-black/50 place-items-center group-hover:opacity-100 group-hover:top-[1.25rem]"
|
className="absolute z-20 flex w-8 h-8 p-2 text-white transition-all rounded-full delay-[20ms] opacity-0 top-1 right-2 dark:bg-black/50 place-items-center group-hover:opacity-100 group-hover:top-[1.25rem]"
|
||||||
@@ -75,7 +83,7 @@ export const ThemeCover: React.FC<ThemeCoverProps> = ({
|
|||||||
<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">
|
||||||
{theme.coverImage &&
|
{theme.coverImage &&
|
||||||
<img
|
<img
|
||||||
src={theme.coverImage as string}
|
src={(typeof theme.coverImage) == 'string' ? theme.coverImage as string : URL.createObjectURL(theme.coverImage as Blob)}
|
||||||
alt={theme.name}
|
alt={theme.name}
|
||||||
className="absolute inset-0 z-0 object-cover"
|
className="absolute inset-0 z-0 object-cover"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useState, useCallback, forwardRef, useImperativeHandle, ForwardRefExoticComponent, RefAttributes } from 'react';
|
import React, { useEffect, useState, useCallback, forwardRef, useImperativeHandle, ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||||
import { listThemes, deleteTheme, setTheme, disableTheme } from '../hooks/ThemeManagment';
|
import { listThemes, deleteTheme, setTheme, disableTheme, getDownloadedThemes } from '../hooks/ThemeManagment';
|
||||||
import { ThemeCover } from './ThemeCover';
|
import { ThemeCover } from './ThemeCover';
|
||||||
import Browser from 'webextension-polyfill';
|
import Browser from 'webextension-polyfill';
|
||||||
import { CustomTheme } from '../types/CustomThemes';
|
import { CustomTheme, DownloadedTheme } from '../types/CustomThemes';
|
||||||
import { useSettingsContext } from '../SettingsContext';
|
import { useSettingsContext } from '../SettingsContext';
|
||||||
import { SettingsState } from '../types/AppProps';
|
import { SettingsState } from '../types/AppProps';
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ interface ThemeSelectorProps {
|
|||||||
|
|
||||||
const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> & RefAttributes<any>> = forwardRef(({ isEditMode = false }, ref) => {
|
const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> & RefAttributes<any>> = forwardRef(({ isEditMode = false }, ref) => {
|
||||||
const [themes, setThemes] = useState<Omit<CustomTheme, 'CustomImages'>[]>([]);
|
const [themes, setThemes] = useState<Omit<CustomTheme, 'CustomImages'>[]>([]);
|
||||||
|
const [downloadedThemes, setDownloadedThemes] = useState<DownloadedTheme[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
const { settingsState, setSettingsState } = useSettingsContext();
|
const { settingsState, setSettingsState } = useSettingsContext();
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> &
|
|||||||
const { themes, selectedTheme } = await listThemes();
|
const { themes, selectedTheme } = await listThemes();
|
||||||
|
|
||||||
setThemes(themes);
|
setThemes(themes);
|
||||||
|
setDownloadedThemes(await getDownloadedThemes());
|
||||||
setSelectedTheme(selectedTheme ? selectedTheme : '');
|
setSelectedTheme(selectedTheme ? selectedTheme : '');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching themes:', error);
|
console.error('Error fetching themes:', error);
|
||||||
@@ -118,6 +120,18 @@ const ThemeSelector: ForwardRefExoticComponent<Omit<ThemeSelectorProps, "ref"> &
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{downloadedThemes.map((theme) => (
|
||||||
|
<ThemeCover
|
||||||
|
key={theme.id}
|
||||||
|
downloaded={true}
|
||||||
|
theme={theme}
|
||||||
|
isSelected={theme.id === settingsState.selectedTheme}
|
||||||
|
isEditMode={isEditMode}
|
||||||
|
onThemeSelect={handleThemeSelect}
|
||||||
|
onThemeDelete={handleThemeDelete}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => Browser.tabs.create({ url: Browser.runtime.getURL('src/interface/index.html#store')})}
|
onClick={() => Browser.tabs.create({ url: Browser.runtime.getURL('src/interface/index.html#store')})}
|
||||||
className="flex items-center justify-center w-full transition aspect-theme rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white"
|
className="flex items-center justify-center w-full transition aspect-theme rounded-xl bg-zinc-100 dark:bg-zinc-900 dark:text-white"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import browser from 'webextension-polyfill'
|
import browser from 'webextension-polyfill'
|
||||||
import { CustomTheme, ThemeList } from '../types/CustomThemes';
|
import { CustomTheme, DownloadedTheme, ThemeList } from '../types/CustomThemes';
|
||||||
|
import localforage from 'localforage';
|
||||||
|
|
||||||
export const downloadTheme = async (themeName: string, themeURL: string) => {
|
export const downloadTheme = async (themeName: string, themeURL: string) => {
|
||||||
// send message to the background script
|
// send message to the background script
|
||||||
@@ -24,6 +25,29 @@ export const setTheme = async (themeID: string) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getDownloadedThemes = async (): Promise<DownloadedTheme[]> => {
|
||||||
|
// send message to the background script
|
||||||
|
const response: DownloadedTheme[] = await new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let availableThemes = await localforage.getItem('availableThemes') as string[];
|
||||||
|
availableThemes = Array.from(new Set(availableThemes));
|
||||||
|
|
||||||
|
const downloadedThemes: DownloadedTheme[] = [];
|
||||||
|
for (let i = 0; i < availableThemes.length; i++) {
|
||||||
|
let themeData = await localforage.getItem(availableThemes[i]) as DownloadedTheme;
|
||||||
|
|
||||||
|
downloadedThemes.push(themeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(downloadedThemes);
|
||||||
|
} catch(error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
export const listThemes = async (): Promise<ThemeList> => {
|
export const listThemes = async (): Promise<ThemeList> => {
|
||||||
// send message to the background script
|
// send message to the background script
|
||||||
const response: ThemeList = await new Promise((resolve, reject) => {
|
const response: ThemeList = await new Promise((resolve, reject) => {
|
||||||
@@ -72,7 +96,7 @@ export const deleteTheme = async (themeID: string) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendThemeUpdate = (updatedTheme: CustomTheme, saveTheme?: boolean, updateImages?: boolean) => {
|
export const sendThemeUpdate = (updatedTheme: CustomTheme | DownloadedTheme, saveTheme?: boolean, updateImages?: boolean) => {
|
||||||
saveTheme = saveTheme || false;
|
saveTheme = saveTheme || false;
|
||||||
|
|
||||||
const imageDataPromises = updatedTheme.CustomImages.map(async (image) => {
|
const imageDataPromises = updatedTheme.CustomImages.map(async (image) => {
|
||||||
@@ -97,7 +121,7 @@ export const sendThemeUpdate = (updatedTheme: CustomTheme, saveTheme?: boolean,
|
|||||||
CustomImages: imageData,
|
CustomImages: imageData,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (saveTheme && updatedTheme.coverImage instanceof Blob) {
|
if (saveTheme && updatedTheme.coverImage) {
|
||||||
themeData.coverImage = await blobToBase64(updatedTheme.coverImage as Blob);
|
themeData.coverImage = await blobToBase64(updatedTheme.coverImage as Blob);
|
||||||
} else {
|
} else {
|
||||||
themeData.coverImage = null;
|
themeData.coverImage = null;
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ export type CustomTheme = {
|
|||||||
hideThemeName: boolean;
|
hideThemeName: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DownloadedTheme = CustomTheme & {
|
||||||
|
webURL: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type CustomImage = {
|
export type CustomImage = {
|
||||||
id: string;
|
id: string;
|
||||||
blob: Blob;
|
blob: Blob;
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ const DownloadTheme = async (theme: ThemesResponse & { theme: CustomTheme & { im
|
|||||||
images.push({ imageData, imageID });
|
images.push({ imageData, imageID });
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Original Theme", theme);
|
const coverImage = await fetch(
|
||||||
|
`https://betterseqta.pockethost.io/api/files/${theme.collectionId}/${theme.id}/${theme.coverImage}`
|
||||||
|
);
|
||||||
|
|
||||||
// add to temp storage index
|
// add to temp storage index
|
||||||
let availableThemes = await localforage.getItem('availableThemes') as string[];
|
let availableThemes = await localforage.getItem('availableThemes') as string[];
|
||||||
@@ -26,13 +28,14 @@ const DownloadTheme = async (theme: ThemesResponse & { theme: CustomTheme & { im
|
|||||||
}
|
}
|
||||||
localforage.setItem('availableThemes', availableThemes);
|
localforage.setItem('availableThemes', availableThemes);
|
||||||
|
|
||||||
// save the theme to the temp storage
|
|
||||||
localforage.setItem(theme.theme.id, {
|
localforage.setItem(theme.theme.id, {
|
||||||
...theme.theme,
|
...theme.theme,
|
||||||
images: theme.theme.images.map((image) => {
|
webURL: theme.id,
|
||||||
|
coverImage: await coverImage.blob(),
|
||||||
|
CustomImages: theme.theme.images.map((image) => {
|
||||||
return {
|
return {
|
||||||
...image,
|
...image,
|
||||||
imageData: images.find((i) => i.imageID.split('_')[0] === image.id)?.imageData
|
blob: images.find((i) => i.imageID.split('_')[0] === image.id)?.imageData
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user