mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
Merge branch 'feature/custom-themes' of github.com:hydralauncher/hydra into feature/custom-themes
This commit is contained in:
commit
74c7668510
10 changed files with 83 additions and 62 deletions
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
|
@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) =>
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
3
src/renderer/src/declaration.d.ts
vendored
3
src/renderer/src/declaration.d.ts
vendored
|
@ -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>;
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue