Merge branch 'feature/custom-themes' of github.com:hydralauncher/hydra into feature/custom-themes

This commit is contained in:
Chubby Granny Chaser 2025-02-16 19:13:34 +00:00
commit 74c7668510
No known key found for this signature in database
10 changed files with 83 additions and 62 deletions

View file

@ -87,7 +87,6 @@ import "./themes/get-custom-theme-by-id";
import "./themes/get-active-custom-theme"; import "./themes/get-active-custom-theme";
import "./themes/css-injector"; import "./themes/css-injector";
import "./themes/close-editor-window"; import "./themes/close-editor-window";
import "./themes/import-theme";
import "./themes/toggle-custom-theme"; import "./themes/toggle-custom-theme";
import { isPortableVersion } from "@main/helpers"; import { isPortableVersion } from "@main/helpers";

View file

@ -1,12 +0,0 @@
import { registerEvent } from "../register-event";
import { WindowManager } from "@main/services";
const importTheme = async (
_event: Electron.IpcMainInvokeEvent,
theme: string,
author: string
) => {
WindowManager.mainWindow?.webContents.send("import-theme", theme, author);
};
registerEvent("importTheme", importTheme);

View file

@ -98,10 +98,8 @@ const handleDeepLinkPath = (uri?: string) => {
const authorCode = url.searchParams.get("author"); const authorCode = url.searchParams.get("author");
if (themeName && authorCode) { if (themeName && authorCode) {
WindowManager.mainWindow?.webContents.send( WindowManager.redirect(
"import-theme", `settings?theme=${themeName}&author=${authorCode}`
themeName,
authorCode
); );
} }
} }

View file

@ -362,15 +362,6 @@ contextBridge.exposeInMainWorld("electron", {
getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"), getActiveCustomTheme: () => ipcRenderer.invoke("getActiveCustomTheme"),
toggleCustomTheme: (themeId: string, isActive: boolean) => toggleCustomTheme: (themeId: string, isActive: boolean) =>
ipcRenderer.invoke("toggleCustomTheme", themeId, isActive), ipcRenderer.invoke("toggleCustomTheme", themeId, isActive),
onImportTheme: (cb: (theme: string, author: string) => void) => {
const listener = (
_event: Electron.IpcRendererEvent,
theme: string,
author: string
) => cb(theme, author);
ipcRenderer.on("import-theme", listener);
return () => ipcRenderer.removeListener("import-theme", listener);
},
/* Editor */ /* Editor */
openEditorWindow: (themeId: string) => openEditorWindow: (themeId: string) =>

View file

@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef } from "react";
import achievementSound from "@renderer/assets/audio/achievement.wav"; import achievementSound from "@renderer/assets/audio/achievement.wav";
import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components";
@ -30,7 +30,6 @@ import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-m
import { injectCustomCss } from "./helpers"; import { injectCustomCss } from "./helpers";
import "./app.scss"; import "./app.scss";
import { ImportThemeModal } from "./pages/settings/aparence/modals/import-theme-modal";
export interface AppProps { export interface AppProps {
children: React.ReactNode; children: React.ReactNode;
@ -39,12 +38,6 @@ export interface AppProps {
export function App() { export function App() {
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
const { updateLibrary, library } = useLibrary(); const { updateLibrary, library } = useLibrary();
const [isImportThemeModalVisible, setIsImportThemeModalVisible] =
useState(false);
const [importTheme, setImportTheme] = useState<{
theme: string;
author: string;
} | null>(null);
const { t } = useTranslation("app"); const { t } = useTranslation("app");
@ -278,15 +271,6 @@ export function App() {
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
useEffect(() => {
const unsubscribe = window.electron.onImportTheme((theme, author) => {
setImportTheme({ theme, author });
setIsImportThemeModalVisible(true);
});
return () => unsubscribe();
}, []);
const handleToastClose = useCallback(() => { const handleToastClose = useCallback(() => {
dispatch(closeToast()); dispatch(closeToast());
}, [dispatch]); }, [dispatch]);
@ -328,16 +312,6 @@ export function App() {
/> />
)} )}
{importTheme && (
<ImportThemeModal
visible={isImportThemeModalVisible}
onClose={() => setIsImportThemeModalVisible(false)}
onThemeImported={() => setIsImportThemeModalVisible(false)}
themeName={importTheme.theme}
authorCode={importTheme.author}
/>
)}
<main> <main>
<Sidebar /> <Sidebar />

View file

@ -13,6 +13,8 @@ export interface SettingsContext {
currentCategoryIndex: number; currentCategoryIndex: number;
blockedUsers: UserBlocks["blocks"]; blockedUsers: UserBlocks["blocks"];
fetchBlockedUsers: () => Promise<void>; fetchBlockedUsers: () => Promise<void>;
appearanceTheme: string | null;
appearanceAuthor: string | null;
} }
export const settingsContext = createContext<SettingsContext>({ export const settingsContext = createContext<SettingsContext>({
@ -23,6 +25,8 @@ export const settingsContext = createContext<SettingsContext>({
currentCategoryIndex: 0, currentCategoryIndex: 0,
blockedUsers: [], blockedUsers: [],
fetchBlockedUsers: async () => {}, fetchBlockedUsers: async () => {},
appearanceTheme: null,
appearanceAuthor: null,
}); });
const { Provider } = settingsContext; const { Provider } = settingsContext;
@ -37,12 +41,16 @@ export function SettingsContextProvider({
}: Readonly<SettingsContextProviderProps>) { }: Readonly<SettingsContextProviderProps>) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [sourceUrl, setSourceUrl] = useState<string | null>(null); const [sourceUrl, setSourceUrl] = useState<string | null>(null);
const [appearanceTheme, setAppearanceTheme] = useState<string | null>(null);
const [appearanceAuthor, setAppearanceAuthor] = useState<string | null>(null);
const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0);
const [blockedUsers, setBlockedUsers] = useState<UserBlocks["blocks"]>([]); const [blockedUsers, setBlockedUsers] = useState<UserBlocks["blocks"]>([]);
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const defaultSourceUrl = searchParams.get("urls"); const defaultSourceUrl = searchParams.get("urls");
const defaultAppearanceTheme = searchParams.get("theme");
const defaultAppearanceAuthor = searchParams.get("author");
useEffect(() => { useEffect(() => {
if (sourceUrl) setCurrentCategoryIndex(2); if (sourceUrl) setCurrentCategoryIndex(2);
@ -54,6 +62,17 @@ export function SettingsContextProvider({
} }
}, [defaultSourceUrl]); }, [defaultSourceUrl]);
useEffect(() => {
if (appearanceTheme) setCurrentCategoryIndex(3);
}, [appearanceTheme]);
useEffect(() => {
if (defaultAppearanceTheme && defaultAppearanceAuthor) {
setAppearanceTheme(defaultAppearanceTheme);
setAppearanceAuthor(defaultAppearanceAuthor);
}
}, [defaultAppearanceTheme, defaultAppearanceAuthor]);
const fetchBlockedUsers = useCallback(async () => { const fetchBlockedUsers = useCallback(async () => {
const blockedUsers = await window.electron.getBlockedUsers(12, 0); const blockedUsers = await window.electron.getBlockedUsers(12, 0);
setBlockedUsers(blockedUsers.blocks); setBlockedUsers(blockedUsers.blocks);
@ -82,6 +101,8 @@ export function SettingsContextProvider({
currentCategoryIndex, currentCategoryIndex,
sourceUrl, sourceUrl,
blockedUsers, blockedUsers,
appearanceTheme,
appearanceAuthor,
}} }}
> >
{children} {children}

View file

@ -290,9 +290,6 @@ declare global {
getCustomThemeById: (themeId: string) => Promise<Theme | null>; getCustomThemeById: (themeId: string) => Promise<Theme | null>;
getActiveCustomTheme: () => Promise<Theme | null>; getActiveCustomTheme: () => Promise<Theme | null>;
toggleCustomTheme: (themeId: string, isActive: boolean) => Promise<void>; toggleCustomTheme: (themeId: string, isActive: boolean) => Promise<void>;
onImportTheme: (
cb: (theme: string, author: string) => void
) => () => Electron.IpcRenderer;
/* Editor */ /* Editor */
openEditorWindow: (themeId: string) => Promise<void>; openEditorWindow: (themeId: string) => Promise<void>;

View file

@ -5,7 +5,7 @@ import "./modals.scss";
import { Theme } from "@types"; import { Theme } from "@types";
import { injectCustomCss, removeCustomCss } from "@renderer/helpers"; import { injectCustomCss, removeCustomCss } from "@renderer/helpers";
import { useToast } from "@renderer/hooks"; import { useToast } from "@renderer/hooks";
import { UserProfile } from "@types";
interface ImportThemeModalProps { interface ImportThemeModalProps {
visible: boolean; visible: boolean;
onClose: () => void; onClose: () => void;
@ -24,13 +24,18 @@ export const ImportThemeModal = ({
const { t } = useTranslation("settings"); const { t } = useTranslation("settings");
const { showSuccessToast, showErrorToast } = useToast(); const { showSuccessToast, showErrorToast } = useToast();
let author: UserProfile | null = null;
window.electron.getUser(authorCode).then((user) => {
author = user;
});
const handleImportTheme = async () => { const handleImportTheme = async () => {
const theme: Theme = { const theme: Theme = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
name: themeName, name: themeName,
isActive: false, isActive: false,
author: authorCode, author: author?.id,
authorName: "spectre", authorName: author?.displayName,
code: `https://hydrathemes.shop/themes/${themeName}.css`, code: `https://hydrathemes.shop/themes/${themeName}.css`,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),

View file

@ -2,9 +2,24 @@ import { useEffect, useState } from "react";
import "./settings-appearance.scss"; import "./settings-appearance.scss";
import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index"; import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index";
import type { Theme } from "@types"; import type { Theme } from "@types";
import { ImportThemeModal } from "./modals/import-theme-modal";
export const SettingsAppearance = () => { interface SettingsAppearanceProps {
appearanceTheme: string | null;
appearanceAuthor: string | null;
}
export const SettingsAppearance = ({
appearanceTheme,
appearanceAuthor,
}: SettingsAppearanceProps) => {
const [themes, setThemes] = useState<Theme[]>([]); const [themes, setThemes] = useState<Theme[]>([]);
const [isImportThemeModalVisible, setIsImportThemeModalVisible] =
useState(false);
const [importTheme, setImportTheme] = useState<{
theme: string;
author: string;
} | null>(null);
const loadThemes = async () => { const loadThemes = async () => {
const themesList = await window.electron.getAllCustomThemes(); const themesList = await window.electron.getAllCustomThemes();
@ -23,6 +38,16 @@ export const SettingsAppearance = () => {
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
useEffect(() => {
if (appearanceTheme && appearanceAuthor) {
setIsImportThemeModalVisible(true);
setImportTheme({
theme: appearanceTheme,
author: appearanceAuthor,
});
}
}, [appearanceTheme, appearanceAuthor]);
return ( return (
<div className="settings-appearance"> <div className="settings-appearance">
<ThemeActions onListUpdated={loadThemes} themesCount={themes.length} /> <ThemeActions onListUpdated={loadThemes} themesCount={themes.length} />
@ -46,6 +71,19 @@ export const SettingsAppearance = () => {
)) ))
)} )}
</div> </div>
{importTheme && (
<ImportThemeModal
visible={isImportThemeModalVisible}
onClose={() => setIsImportThemeModalVisible(false)}
onThemeImported={() => {
setIsImportThemeModalVisible(false);
loadThemes();
}}
themeName={importTheme.theme}
authorCode={importTheme.author}
/>
)}
</div> </div>
); );
}; };

View file

@ -61,7 +61,12 @@ export default function Settings() {
return ( return (
<SettingsContextProvider> <SettingsContextProvider>
<SettingsContextConsumer> <SettingsContextConsumer>
{({ currentCategoryIndex, setCurrentCategoryIndex }) => { {({
currentCategoryIndex,
setCurrentCategoryIndex,
appearanceTheme,
appearanceAuthor,
}) => {
const renderCategory = () => { const renderCategory = () => {
if (currentCategoryIndex === 0) { if (currentCategoryIndex === 0) {
return <SettingsGeneral />; return <SettingsGeneral />;
@ -76,7 +81,12 @@ export default function Settings() {
} }
if (currentCategoryIndex === 3) { if (currentCategoryIndex === 3) {
return <SettingsAppearance />; return (
<SettingsAppearance
appearanceTheme={appearanceTheme}
appearanceAuthor={appearanceAuthor}
/>
);
} }
if (currentCategoryIndex === 4) { if (currentCategoryIndex === 4) {