mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
feat: use leveldb instead of localStorage
This commit is contained in:
parent
58f63cab44
commit
3e2d7a751c
@ -2,31 +2,63 @@ import { GlobeIcon, TrashIcon } from "@primer/octicons-react";
|
|||||||
import { PlusIcon } from "@primer/octicons-react";
|
import { PlusIcon } from "@primer/octicons-react";
|
||||||
import { Button } from "@renderer/components/button/button";
|
import { Button } from "@renderer/components/button/button";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { AddThemeModal, DeleteAllThemesModal } from "../index";
|
||||||
import "./theme-actions.scss";
|
import "./theme-actions.scss";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export const ThemeActions = () => {
|
interface ThemeActionsProps {
|
||||||
|
onListUpdated: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThemeActions = ({ onListUpdated }: ThemeActionsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [addThemeModalVisible, setAddThemeModalVisible] = useState(false);
|
||||||
|
const [deleteAllThemesModalVisible, setDeleteAllThemesModalVisible] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-appearance__actions">
|
<>
|
||||||
<div className="settings-appearance__actions-left">
|
<AddThemeModal
|
||||||
<Button theme="primary" className="settings-appearance__button">
|
visible={addThemeModalVisible}
|
||||||
<GlobeIcon />
|
onClose={() => setAddThemeModalVisible(false)}
|
||||||
{t("web_store")}
|
onThemeAdded={onListUpdated}
|
||||||
</Button>
|
/>
|
||||||
|
|
||||||
<Button theme="danger" className="settings-appearance__button">
|
<DeleteAllThemesModal
|
||||||
<TrashIcon />
|
visible={deleteAllThemesModalVisible}
|
||||||
{t("clear_themes")}
|
onClose={() => setDeleteAllThemesModalVisible(false)}
|
||||||
</Button>
|
onThemesDeleted={onListUpdated}
|
||||||
</div>
|
/>
|
||||||
|
|
||||||
<div className="settings-appearance__actions-right">
|
<div className="settings-appearance__actions">
|
||||||
<Button theme="outline" className="settings-appearance__button">
|
<div className="settings-appearance__actions-left">
|
||||||
<PlusIcon />
|
<Button theme="primary" className="settings-appearance__button">
|
||||||
{t("add_theme")}
|
<GlobeIcon />
|
||||||
</Button>
|
{t("web_store")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
theme="danger"
|
||||||
|
className="settings-appearance__button"
|
||||||
|
onClick={() => setDeleteAllThemesModalVisible(true)}
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
{t("clear_themes")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="settings-appearance__actions-right">
|
||||||
|
<Button
|
||||||
|
theme="outline"
|
||||||
|
className="settings-appearance__button"
|
||||||
|
onClick={() => setAddThemeModalVisible(true)}
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
{t("add_theme")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,84 +1,90 @@
|
|||||||
import { PencilIcon, TrashIcon } from "@primer/octicons-react";
|
import { PencilIcon, TrashIcon } from "@primer/octicons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button } from "@renderer/components/button/button";
|
import { Button } from "@renderer/components/button/button";
|
||||||
import type { Theme } from "../themes-manager";
|
import type { Theme } from "@types";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./theme-card.scss";
|
import "./theme-card.scss";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { DeleteThemeModal } from "../modals/delete-theme-modal";
|
||||||
|
|
||||||
interface ThemeCardProps {
|
interface ThemeCardProps {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
handleSetTheme: (themeId: string) => void;
|
onListUpdated: () => void;
|
||||||
handleDeleteTheme: (themeId: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemeCard = ({
|
export const ThemeCard = ({ theme, onListUpdated }: ThemeCardProps) => {
|
||||||
theme,
|
|
||||||
handleSetTheme,
|
|
||||||
handleDeleteTheme,
|
|
||||||
}: ThemeCardProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [deleteThemeModalVisible, setDeleteThemeModalVisible] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`theme-card ${theme.isActive ? "theme-card--active" : ""}`}
|
<DeleteThemeModal
|
||||||
key={theme.name}
|
visible={deleteThemeModalVisible}
|
||||||
>
|
onClose={() => setDeleteThemeModalVisible(false)}
|
||||||
<div className="theme-card__header">
|
onThemeDeleted={onListUpdated}
|
||||||
<div className="theme-card__header__title">{theme.name}</div>
|
themeId={theme.id}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="theme-card__header__colors">
|
<div
|
||||||
{Object.entries(theme.colors).map(([key, color]) => (
|
className={`theme-card ${theme.isActive ? "theme-card--active" : ""}`}
|
||||||
<div
|
key={theme.name}
|
||||||
title={color}
|
>
|
||||||
style={{ backgroundColor: color }}
|
<div className="theme-card__header">
|
||||||
className="theme-card__header__colors__color"
|
<div className="theme-card__header__title">{theme.name}</div>
|
||||||
key={key}
|
|
||||||
|
<div className="theme-card__header__colors">
|
||||||
|
{Object.entries(theme.colors).map(([key, color]) => (
|
||||||
|
<div
|
||||||
|
title={color}
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
className="theme-card__header__colors__color"
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
{/* color circle */}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{theme.author && theme.author && (
|
||||||
|
<p className="theme-card__author">
|
||||||
|
{t("by")}
|
||||||
|
|
||||||
|
<span
|
||||||
|
className="theme-card__author__name"
|
||||||
|
onClick={() => navigate(`/profile/${theme.author}`)}
|
||||||
>
|
>
|
||||||
{/* color circle */}
|
{theme.author}
|
||||||
</div>
|
</span>
|
||||||
))}
|
</p>
|
||||||
</div>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{theme.author && theme.authorId && (
|
<div className="theme-card__actions">
|
||||||
<p className="theme-card__author">
|
<div className="theme-card__actions__left">
|
||||||
{t("by")}
|
{theme.isActive ? (
|
||||||
|
<Button theme="dark">{t("unset_theme ")}</Button>
|
||||||
|
) : (
|
||||||
|
<Button theme="outline">{t("set_theme")}</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<span
|
<div className="theme-card__actions__right">
|
||||||
className="theme-card__author__name"
|
<Button title={t("edit_theme")} theme="outline">
|
||||||
onClick={() => navigate(`/profile/${theme.authorId}`)}
|
<PencilIcon />
|
||||||
>
|
|
||||||
{theme.author}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="theme-card__actions">
|
|
||||||
<div className="theme-card__actions__left">
|
|
||||||
{theme.isActive ? (
|
|
||||||
<Button theme="dark">{t("unset_theme ")}</Button>
|
|
||||||
) : (
|
|
||||||
<Button onClick={() => handleSetTheme(theme.id)} theme="outline">
|
|
||||||
{t("set_theme")}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="theme-card__actions__right">
|
<Button
|
||||||
<Button title={t("edit_theme")} theme="outline">
|
onClick={() => setDeleteThemeModalVisible(true)}
|
||||||
<PencilIcon />
|
title={t("delete_theme")}
|
||||||
</Button>
|
theme="outline"
|
||||||
|
>
|
||||||
<Button
|
<TrashIcon />
|
||||||
onClick={() => handleDeleteTheme(theme.id)}
|
</Button>
|
||||||
title={t("delete_theme")}
|
</div>
|
||||||
theme="outline"
|
|
||||||
>
|
|
||||||
<TrashIcon />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,26 +1,36 @@
|
|||||||
import { AlertIcon } from "@primer/octicons-react";
|
import { AlertIcon } from "@primer/octicons-react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import "./theme-placeholder.scss";
|
import "./theme-placeholder.scss";
|
||||||
|
import { AddThemeModal } from "../modals/add-theme-modal";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface ThemePlaceholderProps {
|
interface ThemePlaceholderProps {
|
||||||
setAddThemeModalVisible: (visible: boolean) => void;
|
onListUpdated: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemePlaceholder = ({
|
export const ThemePlaceholder = ({ onListUpdated }: ThemePlaceholderProps) => {
|
||||||
setAddThemeModalVisible,
|
const { t } = useTranslation("settings");
|
||||||
}: ThemePlaceholderProps) => {
|
|
||||||
const { t } = useTranslation();
|
const [addThemeModalVisible, setAddThemeModalVisible] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<>
|
||||||
className="theme-placeholder"
|
<AddThemeModal
|
||||||
onClick={() => setAddThemeModalVisible(true)}
|
visible={addThemeModalVisible}
|
||||||
>
|
onClose={() => setAddThemeModalVisible(false)}
|
||||||
<div className="theme-placeholder__icon">
|
onThemeAdded={onListUpdated}
|
||||||
<AlertIcon />
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="theme-placeholder__text">{t("no_themes")}</p>
|
<button
|
||||||
</button>
|
className="theme-placeholder"
|
||||||
|
onClick={() => setAddThemeModalVisible(true)}
|
||||||
|
>
|
||||||
|
<div className="theme-placeholder__icon">
|
||||||
|
<AlertIcon />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="theme-placeholder__text">{t("no_themes")}</p>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,54 @@
|
|||||||
import { Modal } from "@renderer/components/modal/modal";
|
import { Modal } from "@renderer/components/modal/modal";
|
||||||
import { TextField } from "@renderer/components/text-field/text-field";
|
import { TextField } from "@renderer/components/text-field/text-field";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import "./modals.scss";
|
|
||||||
import { Button } from "@renderer/components/button/button";
|
import { Button } from "@renderer/components/button/button";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import "./modals.scss";
|
||||||
|
|
||||||
interface AddThemeModalProps {
|
interface AddThemeModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onThemeAdded: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddThemeModal = ({ visible, onClose }: AddThemeModalProps) => {
|
export const AddThemeModal = ({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
onThemeAdded,
|
||||||
|
}: AddThemeModalProps) => {
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
const [name, setName] = useState("");
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const [themeName, setThemeName] = useState("");
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!name || name.length < 3) {
|
||||||
|
setError(t("theme_name_error_hint"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
name,
|
||||||
|
isActive: false,
|
||||||
|
colors: {
|
||||||
|
accent: "#c0c1c7",
|
||||||
|
background: "#1c1c1c",
|
||||||
|
surface: "#151515",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await window.electron.addCustomTheme(theme);
|
||||||
|
setName("");
|
||||||
|
setError("");
|
||||||
|
onThemeAdded();
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@ -26,12 +61,14 @@ export const AddThemeModal = ({ visible, onClose }: AddThemeModalProps) => {
|
|||||||
<TextField
|
<TextField
|
||||||
label={t("theme_name")}
|
label={t("theme_name")}
|
||||||
placeholder={t("insert_theme_name")}
|
placeholder={t("insert_theme_name")}
|
||||||
hint={t("theme_name_hint")}
|
value={name}
|
||||||
value={themeName}
|
onChange={(e) => setName(e.target.value)}
|
||||||
onChange={(e) => setThemeName(e.target.value)}
|
hint={error}
|
||||||
|
error={!!error}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button theme="primary" onClick={onClose}>
|
<Button theme="primary" onClick={handleSubmit}>
|
||||||
{t("add_theme")}
|
{t("add_theme")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,14 +6,22 @@ import "./modals.scss";
|
|||||||
interface DeleteAllThemesModalProps {
|
interface DeleteAllThemesModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onThemesDeleted: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteAllThemesModal = ({
|
export const DeleteAllThemesModal = ({
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
|
onThemesDeleted,
|
||||||
}: DeleteAllThemesModalProps) => {
|
}: DeleteAllThemesModalProps) => {
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
const handleDeleteAllThemes = async () => {
|
||||||
|
await window.electron.deleteAllCustomThemes();
|
||||||
|
onClose();
|
||||||
|
onThemesDeleted();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@ -22,7 +30,7 @@ export const DeleteAllThemesModal = ({
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<div className="delete-all-themes-modal__container">
|
<div className="delete-all-themes-modal__container">
|
||||||
<Button theme="outline" onClick={onClose}>
|
<Button theme="outline" onClick={handleDeleteAllThemes}>
|
||||||
{t("delete_all_themes")}
|
{t("delete_all_themes")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
@ -7,14 +7,22 @@ interface DeleteThemeModalProps {
|
|||||||
visible: boolean;
|
visible: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
themeId: string;
|
themeId: string;
|
||||||
|
onThemeDeleted: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteThemeModal = ({
|
export const DeleteThemeModal = ({
|
||||||
visible,
|
visible,
|
||||||
onClose,
|
onClose,
|
||||||
|
themeId,
|
||||||
|
onThemeDeleted,
|
||||||
}: DeleteThemeModalProps) => {
|
}: DeleteThemeModalProps) => {
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
const handleDeleteTheme = async () => {
|
||||||
|
await window.electron.deleteCustomTheme(themeId);
|
||||||
|
onThemeDeleted();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
@ -23,7 +31,7 @@ export const DeleteThemeModal = ({
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
>
|
>
|
||||||
<div className="delete-all-themes-modal__container">
|
<div className="delete-all-themes-modal__container">
|
||||||
<Button theme="outline" onClick={onClose}>
|
<Button theme="outline" onClick={handleDeleteTheme}>
|
||||||
{t("delete_theme")}
|
{t("delete_theme")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
@ -1,12 +1,39 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
import "./settings-appearance.scss";
|
import "./settings-appearance.scss";
|
||||||
import { ThemeActions } from "./index";
|
import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index";
|
||||||
|
import type { Theme } from "@types";
|
||||||
|
|
||||||
export const SettingsAppearance = () => {
|
export const SettingsAppearance = () => {
|
||||||
|
const [themes, setThemes] = useState<Theme[]>([]);
|
||||||
|
|
||||||
|
const loadThemes = async () => {
|
||||||
|
const themesList = await window.electron.getAllCustomThemes();
|
||||||
|
setThemes(themesList);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadThemes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-appearance">
|
<div className="settings-appearance">
|
||||||
<p className="settings-appearance__description">Appearance</p>
|
<p className="settings-appearance__description">Appearance</p>
|
||||||
|
|
||||||
<ThemeActions />
|
<ThemeActions onListUpdated={loadThemes} />
|
||||||
|
|
||||||
|
<div className="settings-appearance__themes">
|
||||||
|
{!themes.length ? (
|
||||||
|
<ThemePlaceholder onListUpdated={loadThemes} />
|
||||||
|
) : (
|
||||||
|
themes.map((theme) => (
|
||||||
|
<ThemeCard
|
||||||
|
key={theme.id}
|
||||||
|
theme={theme}
|
||||||
|
onListUpdated={loadThemes}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user