mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +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 { Button } from "@renderer/components/button/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AddThemeModal, DeleteAllThemesModal } from "../index";
|
||||
import "./theme-actions.scss";
|
||||
import { useState } from "react";
|
||||
|
||||
export const ThemeActions = () => {
|
||||
interface ThemeActionsProps {
|
||||
onListUpdated: () => void;
|
||||
}
|
||||
|
||||
export const ThemeActions = ({ onListUpdated }: ThemeActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [addThemeModalVisible, setAddThemeModalVisible] = useState(false);
|
||||
const [deleteAllThemesModalVisible, setDeleteAllThemesModalVisible] =
|
||||
useState(false);
|
||||
|
||||
return (
|
||||
<div className="settings-appearance__actions">
|
||||
<div className="settings-appearance__actions-left">
|
||||
<Button theme="primary" className="settings-appearance__button">
|
||||
<GlobeIcon />
|
||||
{t("web_store")}
|
||||
</Button>
|
||||
<>
|
||||
<AddThemeModal
|
||||
visible={addThemeModalVisible}
|
||||
onClose={() => setAddThemeModalVisible(false)}
|
||||
onThemeAdded={onListUpdated}
|
||||
/>
|
||||
|
||||
<Button theme="danger" className="settings-appearance__button">
|
||||
<TrashIcon />
|
||||
{t("clear_themes")}
|
||||
</Button>
|
||||
</div>
|
||||
<DeleteAllThemesModal
|
||||
visible={deleteAllThemesModalVisible}
|
||||
onClose={() => setDeleteAllThemesModalVisible(false)}
|
||||
onThemesDeleted={onListUpdated}
|
||||
/>
|
||||
|
||||
<div className="settings-appearance__actions-right">
|
||||
<Button theme="outline" className="settings-appearance__button">
|
||||
<PlusIcon />
|
||||
{t("add_theme")}
|
||||
</Button>
|
||||
<div className="settings-appearance__actions">
|
||||
<div className="settings-appearance__actions-left">
|
||||
<Button theme="primary" className="settings-appearance__button">
|
||||
<GlobeIcon />
|
||||
{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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,84 +1,90 @@
|
||||
import { PencilIcon, TrashIcon } from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 "./theme-card.scss";
|
||||
import { useState } from "react";
|
||||
import { DeleteThemeModal } from "../modals/delete-theme-modal";
|
||||
|
||||
interface ThemeCardProps {
|
||||
theme: Theme;
|
||||
handleSetTheme: (themeId: string) => void;
|
||||
handleDeleteTheme: (themeId: string) => void;
|
||||
onListUpdated: () => void;
|
||||
}
|
||||
|
||||
export const ThemeCard = ({
|
||||
theme,
|
||||
handleSetTheme,
|
||||
handleDeleteTheme,
|
||||
}: ThemeCardProps) => {
|
||||
export const ThemeCard = ({ theme, onListUpdated }: ThemeCardProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [deleteThemeModalVisible, setDeleteThemeModalVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`theme-card ${theme.isActive ? "theme-card--active" : ""}`}
|
||||
key={theme.name}
|
||||
>
|
||||
<div className="theme-card__header">
|
||||
<div className="theme-card__header__title">{theme.name}</div>
|
||||
<>
|
||||
<DeleteThemeModal
|
||||
visible={deleteThemeModalVisible}
|
||||
onClose={() => setDeleteThemeModalVisible(false)}
|
||||
onThemeDeleted={onListUpdated}
|
||||
themeId={theme.id}
|
||||
/>
|
||||
|
||||
<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}
|
||||
<div
|
||||
className={`theme-card ${theme.isActive ? "theme-card--active" : ""}`}
|
||||
key={theme.name}
|
||||
>
|
||||
<div className="theme-card__header">
|
||||
<div className="theme-card__header__title">{theme.name}</div>
|
||||
|
||||
<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 */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{theme.author}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{theme.author && theme.authorId && (
|
||||
<p className="theme-card__author">
|
||||
{t("by")}
|
||||
<div className="theme-card__actions">
|
||||
<div className="theme-card__actions__left">
|
||||
{theme.isActive ? (
|
||||
<Button theme="dark">{t("unset_theme ")}</Button>
|
||||
) : (
|
||||
<Button theme="outline">{t("set_theme")}</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<span
|
||||
className="theme-card__author__name"
|
||||
onClick={() => navigate(`/profile/${theme.authorId}`)}
|
||||
>
|
||||
{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")}
|
||||
<div className="theme-card__actions__right">
|
||||
<Button title={t("edit_theme")} theme="outline">
|
||||
<PencilIcon />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="theme-card__actions__right">
|
||||
<Button title={t("edit_theme")} theme="outline">
|
||||
<PencilIcon />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => handleDeleteTheme(theme.id)}
|
||||
title={t("delete_theme")}
|
||||
theme="outline"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => setDeleteThemeModalVisible(true)}
|
||||
title={t("delete_theme")}
|
||||
theme="outline"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,26 +1,36 @@
|
||||
import { AlertIcon } from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./theme-placeholder.scss";
|
||||
import { AddThemeModal } from "../modals/add-theme-modal";
|
||||
import { useState } from "react";
|
||||
|
||||
interface ThemePlaceholderProps {
|
||||
setAddThemeModalVisible: (visible: boolean) => void;
|
||||
onListUpdated: () => void;
|
||||
}
|
||||
|
||||
export const ThemePlaceholder = ({
|
||||
setAddThemeModalVisible,
|
||||
}: ThemePlaceholderProps) => {
|
||||
const { t } = useTranslation();
|
||||
export const ThemePlaceholder = ({ onListUpdated }: ThemePlaceholderProps) => {
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const [addThemeModalVisible, setAddThemeModalVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<button
|
||||
className="theme-placeholder"
|
||||
onClick={() => setAddThemeModalVisible(true)}
|
||||
>
|
||||
<div className="theme-placeholder__icon">
|
||||
<AlertIcon />
|
||||
</div>
|
||||
<>
|
||||
<AddThemeModal
|
||||
visible={addThemeModalVisible}
|
||||
onClose={() => setAddThemeModalVisible(false)}
|
||||
onThemeAdded={onListUpdated}
|
||||
/>
|
||||
|
||||
<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 { TextField } from "@renderer/components/text-field/text-field";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./modals.scss";
|
||||
import { Button } from "@renderer/components/button/button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import "./modals.scss";
|
||||
|
||||
interface AddThemeModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onThemeAdded: () => void;
|
||||
}
|
||||
|
||||
export const AddThemeModal = ({ visible, onClose }: AddThemeModalProps) => {
|
||||
export const AddThemeModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
onThemeAdded,
|
||||
}: AddThemeModalProps) => {
|
||||
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 (
|
||||
<Modal
|
||||
@ -26,12 +61,14 @@ export const AddThemeModal = ({ visible, onClose }: AddThemeModalProps) => {
|
||||
<TextField
|
||||
label={t("theme_name")}
|
||||
placeholder={t("insert_theme_name")}
|
||||
hint={t("theme_name_hint")}
|
||||
value={themeName}
|
||||
onChange={(e) => setThemeName(e.target.value)}
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
hint={error}
|
||||
error={!!error}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
|
||||
<Button theme="primary" onClick={onClose}>
|
||||
<Button theme="primary" onClick={handleSubmit}>
|
||||
{t("add_theme")}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -6,14 +6,22 @@ import "./modals.scss";
|
||||
interface DeleteAllThemesModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
onThemesDeleted: () => void;
|
||||
}
|
||||
|
||||
export const DeleteAllThemesModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
onThemesDeleted,
|
||||
}: DeleteAllThemesModalProps) => {
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const handleDeleteAllThemes = async () => {
|
||||
await window.electron.deleteAllCustomThemes();
|
||||
onClose();
|
||||
onThemesDeleted();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
@ -22,7 +30,7 @@ export const DeleteAllThemesModal = ({
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="delete-all-themes-modal__container">
|
||||
<Button theme="outline" onClick={onClose}>
|
||||
<Button theme="outline" onClick={handleDeleteAllThemes}>
|
||||
{t("delete_all_themes")}
|
||||
</Button>
|
||||
|
||||
|
@ -7,14 +7,22 @@ interface DeleteThemeModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
themeId: string;
|
||||
onThemeDeleted: () => void;
|
||||
}
|
||||
|
||||
export const DeleteThemeModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
themeId,
|
||||
onThemeDeleted,
|
||||
}: DeleteThemeModalProps) => {
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const handleDeleteTheme = async () => {
|
||||
await window.electron.deleteCustomTheme(themeId);
|
||||
onThemeDeleted();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
@ -23,7 +31,7 @@ export const DeleteThemeModal = ({
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="delete-all-themes-modal__container">
|
||||
<Button theme="outline" onClick={onClose}>
|
||||
<Button theme="outline" onClick={handleDeleteTheme}>
|
||||
{t("delete_theme")}
|
||||
</Button>
|
||||
|
||||
|
@ -1,12 +1,39 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import "./settings-appearance.scss";
|
||||
import { ThemeActions } from "./index";
|
||||
import { ThemeActions, ThemeCard, ThemePlaceholder } from "./index";
|
||||
import type { Theme } from "@types";
|
||||
|
||||
export const SettingsAppearance = () => {
|
||||
const [themes, setThemes] = useState<Theme[]>([]);
|
||||
|
||||
const loadThemes = async () => {
|
||||
const themesList = await window.electron.getAllCustomThemes();
|
||||
setThemes(themesList);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadThemes();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="settings-appearance">
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user