mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
fix: fixing download sources import status
This commit is contained in:
29
.cursorrules
Normal file
29
.cursorrules
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Hydra Project Rules
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
- **Always use `logger` instead of `console` for logging** in both main and renderer processes
|
||||||
|
- In main process: `import { logger } from "@main/services";`
|
||||||
|
- In renderer process: `import { logger } from "@renderer/logger";`
|
||||||
|
- Replace all instances of:
|
||||||
|
- `console.log()` → `logger.log()`
|
||||||
|
- `console.error()` → `logger.error()`
|
||||||
|
- `console.warn()` → `logger.warn()`
|
||||||
|
- `console.info()` → `logger.info()`
|
||||||
|
- `console.debug()` → `logger.debug()`
|
||||||
|
- Do not use `console` for any logging purposes
|
||||||
|
|
||||||
|
## Internationalization (i18n)
|
||||||
|
|
||||||
|
- All user-facing strings must be translated using i18next
|
||||||
|
- Use the `useTranslation` hook in React components: `const { t } = useTranslation("namespace");`
|
||||||
|
- Add new translation keys to `src/locales/en/translation.json`
|
||||||
|
- Never hardcode English strings in the UI code
|
||||||
|
- Placeholder text in form fields must also be translated
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Use ESLint and Prettier for code formatting
|
||||||
|
- Follow TypeScript strict mode conventions
|
||||||
|
- Use async/await instead of promises when possible
|
||||||
|
- Prefer named exports over default exports for utilities and services
|
||||||
@@ -428,6 +428,8 @@
|
|||||||
"validate_download_source": "Validate",
|
"validate_download_source": "Validate",
|
||||||
"remove_download_source": "Remove",
|
"remove_download_source": "Remove",
|
||||||
"add_download_source": "Add source",
|
"add_download_source": "Add source",
|
||||||
|
"adding": "Adding…",
|
||||||
|
"failed_add_download_source": "Failed to add download source. Please try again.",
|
||||||
"download_count_zero": "No download options",
|
"download_count_zero": "No download options",
|
||||||
"download_count_one": "{{countFormatted}} download option",
|
"download_count_one": "{{countFormatted}} download option",
|
||||||
"download_count_other": "{{countFormatted}} download options",
|
"download_count_other": "{{countFormatted}} download options",
|
||||||
@@ -435,9 +437,16 @@
|
|||||||
"add_download_source_description": "Insert the URL of the .json file",
|
"add_download_source_description": "Insert the URL of the .json file",
|
||||||
"download_source_up_to_date": "Up-to-date",
|
"download_source_up_to_date": "Up-to-date",
|
||||||
"download_source_errored": "Errored",
|
"download_source_errored": "Errored",
|
||||||
|
"download_source_pending_matching": "Updating soon",
|
||||||
|
"download_source_matched": "Up to date",
|
||||||
|
"download_source_matching": "Updating",
|
||||||
|
"download_source_failed": "Error",
|
||||||
|
"download_source_no_information": "No information available",
|
||||||
"sync_download_sources": "Sync sources",
|
"sync_download_sources": "Sync sources",
|
||||||
"removed_download_source": "Download source removed",
|
"removed_download_source": "Download source removed",
|
||||||
"removed_download_sources": "Download sources removed",
|
"removed_download_sources": "Download sources removed",
|
||||||
|
"removed_all_download_sources": "All download sources removed",
|
||||||
|
"download_sources_synced_successfully": "All download sources are synced",
|
||||||
"cancel_button_confirmation_delete_all_sources": "No",
|
"cancel_button_confirmation_delete_all_sources": "No",
|
||||||
"confirm_button_confirmation_delete_all_sources": "Yes, delete everything",
|
"confirm_button_confirmation_delete_all_sources": "Yes, delete everything",
|
||||||
"title_confirmation_delete_all_sources": "Delete all download sources",
|
"title_confirmation_delete_all_sources": "Delete all download sources",
|
||||||
@@ -468,6 +477,7 @@
|
|||||||
"seed_after_download_complete": "Seed after download complete",
|
"seed_after_download_complete": "Seed after download complete",
|
||||||
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them",
|
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
|
"hydra_cloud": "Hydra Cloud",
|
||||||
"no_users_blocked": "You have no blocked users",
|
"no_users_blocked": "You have no blocked users",
|
||||||
"subscription_active_until": "Your Hydra Cloud is active until {{date}}",
|
"subscription_active_until": "Your Hydra Cloud is active until {{date}}",
|
||||||
"manage_subscription": "Manage subscription",
|
"manage_subscription": "Manage subscription",
|
||||||
|
|||||||
@@ -423,7 +423,13 @@
|
|||||||
"add_download_source_description": "Insira a URL contendo o arquivo .json",
|
"add_download_source_description": "Insira a URL contendo o arquivo .json",
|
||||||
"download_source_up_to_date": "Sincronizada",
|
"download_source_up_to_date": "Sincronizada",
|
||||||
"download_source_errored": "Falhou",
|
"download_source_errored": "Falhou",
|
||||||
|
"download_source_pending_matching": "Importando em breve",
|
||||||
|
"download_source_matched": "Sincronizada",
|
||||||
|
"download_source_matching": "Sincronizando",
|
||||||
|
"download_source_failed": "Erro",
|
||||||
|
"download_source_no_information": "Sem informações",
|
||||||
"sync_download_sources": "Sincronizar",
|
"sync_download_sources": "Sincronizar",
|
||||||
|
"download_sources_synced_successfully": "Fontes de download sincronizadas",
|
||||||
"removed_download_source": "Fonte removida",
|
"removed_download_source": "Fonte removida",
|
||||||
"removed_download_sources": "Fontes removidas",
|
"removed_download_sources": "Fontes removidas",
|
||||||
"cancel_button_confirmation_delete_all_sources": "Não",
|
"cancel_button_confirmation_delete_all_sources": "Não",
|
||||||
|
|||||||
@@ -252,7 +252,13 @@
|
|||||||
"add_download_source_description": "Insere o URL que contém o ficheiro .json",
|
"add_download_source_description": "Insere o URL que contém o ficheiro .json",
|
||||||
"download_source_up_to_date": "Sincronizada",
|
"download_source_up_to_date": "Sincronizada",
|
||||||
"download_source_errored": "Falhou",
|
"download_source_errored": "Falhou",
|
||||||
|
"download_source_pending_matching": "A atualizar em breve",
|
||||||
|
"download_source_matched": "Atualizado",
|
||||||
|
"download_source_matching": "A atualizar",
|
||||||
|
"download_source_failed": "Erro",
|
||||||
|
"download_source_no_information": "Sem informações",
|
||||||
"sync_download_sources": "Sincronizar",
|
"sync_download_sources": "Sincronizar",
|
||||||
|
"download_sources_synced_successfully": "Fontes de download sincronizadas",
|
||||||
"removed_download_source": "Fonte removida",
|
"removed_download_source": "Fonte removida",
|
||||||
"cancel_button_confirmation_delete_all_sources": "Não",
|
"cancel_button_confirmation_delete_all_sources": "Não",
|
||||||
"confirm_button_confirmation_delete_all_sources": "Sim, apague tudo",
|
"confirm_button_confirmation_delete_all_sources": "Sim, apague tudo",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Button, Modal, TextField } from "@renderer/components";
|
import { Button, Modal, TextField } from "@renderer/components";
|
||||||
import { settingsContext } from "@renderer/context";
|
import { settingsContext } from "@renderer/context";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { logger } from "@renderer/logger";
|
||||||
|
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
@@ -55,10 +56,10 @@ export function AddDownloadSourceModal({
|
|||||||
onClose();
|
onClose();
|
||||||
onAddDownloadSource();
|
onAddDownloadSource();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to add download source:", error);
|
logger.error("Failed to add download source:", error);
|
||||||
setError("url", {
|
setError("url", {
|
||||||
type: "server",
|
type: "server",
|
||||||
message: "Failed to add download source. Please try again.",
|
message: t("failed_add_download_source"),
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export function SettingsAccount() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="settings-account__section">
|
<section className="settings-account__section">
|
||||||
<h3>Hydra Cloud</h3>
|
<h3>{t("hydra_cloud")}</h3>
|
||||||
<div className="settings-account__subscription-info">
|
<div className="settings-account__subscription-info">
|
||||||
{getHydraCloudSectionContent().description}
|
{getHydraCloudSectionContent().description}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
@use "../../scss/globals.scss";
|
@use "../../scss/globals.scss";
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.settings-download-sources {
|
.settings-download-sources {
|
||||||
&__list {
|
&__list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -22,6 +31,17 @@
|
|||||||
&--syncing {
|
&--syncing {
|
||||||
opacity: globals.$disabled-opacity;
|
opacity: globals.$disabled-opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--pending {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__spinner {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-right: calc(globals.$spacing-unit / 2);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__item-header {
|
&__item-header {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { settingsContext } from "@renderer/context";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { setFilters, clearFilters } from "@renderer/features";
|
import { setFilters, clearFilters } from "@renderer/features";
|
||||||
import "./settings-download-sources.scss";
|
import "./settings-download-sources.scss";
|
||||||
|
import { logger } from "@renderer/logger";
|
||||||
|
|
||||||
export function SettingsDownloadSources() {
|
export function SettingsDownloadSources() {
|
||||||
const [
|
const [
|
||||||
@@ -58,6 +59,30 @@ export function SettingsDownloadSources() {
|
|||||||
fetchDownloadSources();
|
fetchDownloadSources();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hasPendingOrMatchingSource = downloadSources.some(
|
||||||
|
(source) =>
|
||||||
|
source.status === DownloadSourceStatus.PendingMatching ||
|
||||||
|
source.status === DownloadSourceStatus.Matching
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasPendingOrMatchingSource || !downloadSources.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intervalId = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await window.electron.syncDownloadSources();
|
||||||
|
const sources = await window.electron.getDownloadSources();
|
||||||
|
setDownloadSources(sources);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to fetch download sources:", error);
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [downloadSources]);
|
||||||
|
|
||||||
const handleRemoveSource = async (downloadSource: DownloadSource) => {
|
const handleRemoveSource = async (downloadSource: DownloadSource) => {
|
||||||
setIsRemovingDownloadSource(true);
|
setIsRemovingDownloadSource(true);
|
||||||
|
|
||||||
@@ -67,7 +92,7 @@ export function SettingsDownloadSources() {
|
|||||||
setDownloadSources(sources as DownloadSource[]);
|
setDownloadSources(sources as DownloadSource[]);
|
||||||
showSuccessToast(t("removed_download_source"));
|
showSuccessToast(t("removed_download_source"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to remove download source:", error);
|
logger.error("Failed to remove download source:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsRemovingDownloadSource(false);
|
setIsRemovingDownloadSource(false);
|
||||||
}
|
}
|
||||||
@@ -82,7 +107,7 @@ export function SettingsDownloadSources() {
|
|||||||
setDownloadSources(sources as DownloadSource[]);
|
setDownloadSources(sources as DownloadSource[]);
|
||||||
showSuccessToast(t("removed_all_download_sources"));
|
showSuccessToast(t("removed_all_download_sources"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to remove all download sources:", error);
|
logger.error("Failed to remove all download sources:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsRemovingDownloadSource(false);
|
setIsRemovingDownloadSource(false);
|
||||||
setShowConfirmationDeleteAllSourcesModal(false);
|
setShowConfirmationDeleteAllSourcesModal(false);
|
||||||
@@ -94,7 +119,7 @@ export function SettingsDownloadSources() {
|
|||||||
const sources = await window.electron.getDownloadSources();
|
const sources = await window.electron.getDownloadSources();
|
||||||
setDownloadSources(sources as DownloadSource[]);
|
setDownloadSources(sources as DownloadSource[]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to refresh download sources:", error);
|
logger.error("Failed to refresh download sources:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,7 +152,7 @@ export function SettingsDownloadSources() {
|
|||||||
|
|
||||||
const navigateToCatalogue = (fingerprint?: string) => {
|
const navigateToCatalogue = (fingerprint?: string) => {
|
||||||
if (!fingerprint) {
|
if (!fingerprint) {
|
||||||
console.error("Cannot navigate: fingerprint is undefined");
|
logger.error("Cannot navigate: fingerprint is undefined");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,16 +227,25 @@ export function SettingsDownloadSources() {
|
|||||||
|
|
||||||
<ul className="settings-download-sources__list">
|
<ul className="settings-download-sources__list">
|
||||||
{downloadSources.map((downloadSource) => {
|
{downloadSources.map((downloadSource) => {
|
||||||
|
const isPendingOrMatching =
|
||||||
|
downloadSource.status === DownloadSourceStatus.PendingMatching ||
|
||||||
|
downloadSource.status === DownloadSourceStatus.Matching;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={downloadSource.id}
|
key={downloadSource.id}
|
||||||
className={`settings-download-sources__item ${isSyncingDownloadSources ? "settings-download-sources__item--syncing" : ""}`}
|
className={`settings-download-sources__item ${isSyncingDownloadSources ? "settings-download-sources__item--syncing" : ""} ${isPendingOrMatching ? "settings-download-sources__item--pending" : ""}`}
|
||||||
>
|
>
|
||||||
<div className="settings-download-sources__item-header">
|
<div className="settings-download-sources__item-header">
|
||||||
<h2>{downloadSource.name}</h2>
|
<h2>{downloadSource.name}</h2>
|
||||||
|
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
<Badge>{statusTitle[downloadSource.status]}</Badge>
|
<Badge>
|
||||||
|
{isPendingOrMatching && (
|
||||||
|
<SyncIcon className="settings-download-sources__spinner" />
|
||||||
|
)}
|
||||||
|
{statusTitle[downloadSource.status]}
|
||||||
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -223,11 +257,13 @@ export function SettingsDownloadSources() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<small>
|
<small>
|
||||||
{t("download_count", {
|
{isPendingOrMatching
|
||||||
count: downloadSource.downloadCount,
|
? t("download_source_no_information")
|
||||||
countFormatted:
|
: t("download_count", {
|
||||||
downloadSource.downloadCount.toLocaleString(),
|
count: downloadSource.downloadCount,
|
||||||
})}
|
countFormatted:
|
||||||
|
downloadSource.downloadCount.toLocaleString(),
|
||||||
|
})}
|
||||||
</small>
|
</small>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export function SettingsRealDebrid() {
|
|||||||
{t("save_changes")}
|
{t("save_changes")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
placeholder="API Token"
|
placeholder={t("api_token")}
|
||||||
hint={
|
hint={
|
||||||
<Trans i18nKey="debrid_api_token_hint" ns="settings">
|
<Trans i18nKey="debrid_api_token_hint" ns="settings">
|
||||||
<Link to={REAL_DEBRID_API_TOKEN_URL} />
|
<Link to={REAL_DEBRID_API_TOKEN_URL} />
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ export function SettingsTorBox() {
|
|||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setForm({ ...form, torBoxApiToken: event.target.value })
|
setForm({ ...form, torBoxApiToken: event.target.value })
|
||||||
}
|
}
|
||||||
placeholder="API Token"
|
placeholder={t("api_token")}
|
||||||
rightContent={
|
rightContent={
|
||||||
<Button type="submit" disabled={isButtonDisabled}>
|
<Button type="submit" disabled={isButtonDisabled}>
|
||||||
{t("save_changes")}
|
{t("save_changes")}
|
||||||
|
|||||||
Reference in New Issue
Block a user