feat: use leveldb instead of localStorage

This commit is contained in:
Hachi-R 2025-01-24 15:29:07 -03:00
parent 58f63cab44
commit 3e2d7a751c
7 changed files with 232 additions and 104 deletions

View File

@ -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> </>
); );
}; };

View File

@ -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> </>
); );
}; };

View File

@ -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>
</>
); );
}; };

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
); );
}; };