mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-02 16:23:48 +03:00
add language selection menu
This commit is contained in:
parent
29d9c43834
commit
72aa822655
9853
package-lock.json
generated
Normal file
9853
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -36,6 +36,7 @@
|
||||
"@reduxjs/toolkit": "^2.2.3",
|
||||
"@vanilla-extract/css": "^1.14.2",
|
||||
"@vanilla-extract/recipes": "^0.5.2",
|
||||
"iso-639-1": "3.1.2",
|
||||
"auto-launch": "^5.0.6",
|
||||
"axios": "^1.6.8",
|
||||
"better-sqlite3": "^9.5.0",
|
||||
|
@ -153,7 +153,8 @@
|
||||
"enable_real_debrid": "Enable Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid_api_token_hint": "You can get your API key <0>here</0>.",
|
||||
"save_changes": "Save changes"
|
||||
"save_changes": "Save changes",
|
||||
"language": "Language"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download complete",
|
||||
|
@ -153,7 +153,8 @@
|
||||
"enable_real_debrid": "Activar Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid_api_token_hint": "Puedes obtener tu clave de API <0>aquí</0>.",
|
||||
"save_changes": "Guardar cambios"
|
||||
"save_changes": "Guardar cambios",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Descarga completada",
|
||||
|
@ -115,7 +115,8 @@
|
||||
"enable_download_notifications": "Quand un téléchargement est terminé",
|
||||
"enable_repack_list_notifications": "Quand un nouveau repack est ajouté",
|
||||
"telemetry": "Télémétrie",
|
||||
"telemetry_description": "Activer les statistiques d'utilisation anonymes"
|
||||
"telemetry_description": "Activer les statistiques d'utilisation anonymes",
|
||||
"language": "Langue"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Téléchargement terminé",
|
||||
|
@ -149,7 +149,8 @@
|
||||
"enable_real_debrid": "Habilitar Real Debrid",
|
||||
"real_debrid": "Real Debrid",
|
||||
"real_debrid_api_token_hint": "Você pode obter sua chave de API <0>aqui</0>.",
|
||||
"save_changes": "Salvar mudanças"
|
||||
"save_changes": "Salvar mudanças",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download concluído",
|
||||
|
@ -8,3 +8,4 @@ export * from "./sidebar/sidebar";
|
||||
export * from "./text-field/text-field";
|
||||
export * from "./checkbox-field/checkbox-field";
|
||||
export * from "./link/link";
|
||||
export * from "./select/select";
|
||||
|
60
src/renderer/src/components/select/select.css.ts
Normal file
60
src/renderer/src/components/select/select.css.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
import { style } from "@vanilla-extract/css";
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
|
||||
export const select = recipe({
|
||||
base: {
|
||||
display: "inline-flex",
|
||||
transition: "all ease 0.2s",
|
||||
width: "fit-content",
|
||||
alignItems: "center",
|
||||
borderRadius: "8px",
|
||||
border: `1px solid ${vars.color.border}`,
|
||||
height: "40px",
|
||||
minHeight: "40px",
|
||||
},
|
||||
variants: {
|
||||
focused: {
|
||||
true: {
|
||||
borderColor: "#DADBE1",
|
||||
},
|
||||
false: {
|
||||
":hover": {
|
||||
borderColor: "rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
primary: {
|
||||
backgroundColor: vars.color.darkBackground,
|
||||
},
|
||||
dark: {
|
||||
backgroundColor: vars.color.background,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const option = style({
|
||||
backgroundColor: vars.color.darkBackground,
|
||||
borderColor: "transparent",
|
||||
borderRadius: "8px",
|
||||
width: "fit-content",
|
||||
height: "100%",
|
||||
outline: "none",
|
||||
color: "#DADBE1",
|
||||
cursor: "default",
|
||||
fontFamily: "inherit",
|
||||
fontSize: vars.size.bodyFontSize,
|
||||
textOverflow: "ellipsis",
|
||||
padding: `${SPACING_UNIT}px`,
|
||||
":focus": {
|
||||
cursor: "text",
|
||||
},
|
||||
});
|
||||
|
||||
export const label = style({
|
||||
marginBottom: `${SPACING_UNIT}px`,
|
||||
display: "block",
|
||||
color: vars.color.bodyText,
|
||||
});
|
46
src/renderer/src/components/select/select.tsx
Normal file
46
src/renderer/src/components/select/select.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import { useId, useState } from "react";
|
||||
import type { RecipeVariants } from "@vanilla-extract/recipes";
|
||||
import * as styles from "./select.css";
|
||||
|
||||
export interface SelectProps
|
||||
extends React.DetailedHTMLProps<
|
||||
React.SelectHTMLAttributes<HTMLSelectElement>,
|
||||
HTMLSelectElement
|
||||
> {
|
||||
theme?: NonNullable<RecipeVariants<typeof styles.select>>["theme"];
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function Select({
|
||||
value,
|
||||
theme = "primary",
|
||||
label,
|
||||
children,
|
||||
onChange,
|
||||
}: SelectProps) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1 }}>
|
||||
{label && (
|
||||
<label htmlFor={id} className={styles.label}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className={styles.select({ focused: isFocused, theme })}>
|
||||
<select
|
||||
id={id}
|
||||
value={value}
|
||||
className={styles.option}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
onChange={onChange}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import ISO6391 from 'iso-639-1'
|
||||
|
||||
import { TextField, Button, CheckboxField } from "@renderer/components";
|
||||
import { TextField, Button, CheckboxField, Select} from "@renderer/components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import * as styles from "./settings-general.css";
|
||||
import type { UserPreferences } from "@types";
|
||||
|
||||
import { changeLanguage } from "i18next";
|
||||
import * as languageResources from "@locales";
|
||||
|
||||
export interface SettingsGeneralProps {
|
||||
userPreferences: UserPreferences | null;
|
||||
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
||||
@ -15,34 +18,41 @@ export function SettingsGeneral({
|
||||
userPreferences,
|
||||
updateUserPreferences,
|
||||
}: SettingsGeneralProps) {
|
||||
const [form, setForm] = useState({
|
||||
downloadsPath: "",
|
||||
downloadNotificationsEnabled: false,
|
||||
repackUpdatesNotificationsEnabled: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (userPreferences) {
|
||||
const {
|
||||
downloadsPath,
|
||||
downloadNotificationsEnabled,
|
||||
repackUpdatesNotificationsEnabled,
|
||||
} = userPreferences;
|
||||
|
||||
window.electron.getDefaultDownloadsPath().then((defaultDownloadsPath) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
downloadsPath: downloadsPath ?? defaultDownloadsPath,
|
||||
downloadNotificationsEnabled,
|
||||
repackUpdatesNotificationsEnabled,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}, [userPreferences]);
|
||||
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const [form, setForm] = useState(
|
||||
{
|
||||
downloadsPath: '',
|
||||
downloadNotificationsEnabled: false,
|
||||
repackUpdatesNotificationsEnabled: false,
|
||||
language: '',
|
||||
}
|
||||
);
|
||||
|
||||
const [defaultDownloadsPath, setdefaultDownloadsPath] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchdefaultDownloadsPath() {
|
||||
setdefaultDownloadsPath(await window.electron.getDefaultDownloadsPath())
|
||||
}
|
||||
|
||||
fetchdefaultDownloadsPath();
|
||||
}, []);
|
||||
|
||||
useEffect(updateFormWithUserPreferences, [userPreferences, defaultDownloadsPath]);
|
||||
|
||||
const handleLanguageChange = (event) => {
|
||||
const value = event.target.value;
|
||||
|
||||
handleChange({ language: value });
|
||||
changeLanguage(value);
|
||||
};
|
||||
|
||||
const handleChange = (values: Partial<typeof form>) => {
|
||||
// TODO: why is the setForm needed if updateUserPreferences already changes the
|
||||
// UserPreferences and useEffect(updateFormWithUserPreferences)
|
||||
// does the setForm((prev) in the callback function?
|
||||
setForm((prev) => ({ ...prev, ...values }));
|
||||
updateUserPreferences(values);
|
||||
};
|
||||
@ -56,10 +66,21 @@ export function SettingsGeneral({
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
handleChange({ downloadsPath: path });
|
||||
updateUserPreferences({ downloadsPath: path });
|
||||
}
|
||||
};
|
||||
|
||||
function updateFormWithUserPreferences(){
|
||||
if (userPreferences) {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
downloadsPath: userPreferences.downloadsPath ?? defaultDownloadsPath,
|
||||
downloadNotificationsEnabled: userPreferences.downloadNotificationsEnabled,
|
||||
repackUpdatesNotificationsEnabled: userPreferences.repackUpdatesNotificationsEnabled,
|
||||
language: userPreferences.language
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.downloadsPathField}>
|
||||
@ -79,28 +100,38 @@ export function SettingsGeneral({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h3>{t("language")}</h3>
|
||||
<>
|
||||
<Select value={form.language} onChange={handleLanguageChange}>
|
||||
{Object.keys(languageResources).map(language => (
|
||||
<option key={language} value={language}>{ISO6391.getName(language)}</option>
|
||||
))}
|
||||
</Select>
|
||||
</>
|
||||
|
||||
<h3>{t("notifications")}</h3>
|
||||
<>
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
checked={form.downloadNotificationsEnabled}
|
||||
onChange={() =>
|
||||
handleChange({
|
||||
downloadNotificationsEnabled: !form.downloadNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
checked={form.downloadNotificationsEnabled}
|
||||
onChange={() =>
|
||||
handleChange({
|
||||
downloadNotificationsEnabled: !form.downloadNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_repack_list_notifications")}
|
||||
checked={form.repackUpdatesNotificationsEnabled}
|
||||
onChange={() =>
|
||||
handleChange({
|
||||
repackUpdatesNotificationsEnabled:
|
||||
!form.repackUpdatesNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<CheckboxField
|
||||
label={t("enable_repack_list_notifications")}
|
||||
checked={form.repackUpdatesNotificationsEnabled}
|
||||
onChange={() =>
|
||||
handleChange({
|
||||
repackUpdatesNotificationsEnabled:
|
||||
!form.repackUpdatesNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -27,32 +27,31 @@ export function Settings() {
|
||||
window.electron.updateUserPreferences(values);
|
||||
};
|
||||
|
||||
const renderCategory = () => {
|
||||
if (currentCategory === "general") {
|
||||
return (
|
||||
<SettingsGeneral
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
function renderCategory() {
|
||||
switch (currentCategory) {
|
||||
case "general":
|
||||
return (
|
||||
<SettingsGeneral
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
case "real_debrid":
|
||||
return (
|
||||
<SettingsRealDebrid
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<SettingsBehavior
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentCategory === "real_debrid") {
|
||||
return (
|
||||
<SettingsRealDebrid
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsBehavior
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
|
Loading…
Reference in New Issue
Block a user