fix: fixing download sources import status

This commit is contained in:
Chubby Granny Chaser
2025-10-27 04:12:19 +00:00
parent 2a90faeb42
commit 2835207d79
10 changed files with 124 additions and 16 deletions

29
.cursorrules Normal file
View 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

View File

@@ -428,6 +428,8 @@
"validate_download_source": "Validate",
"remove_download_source": "Remove",
"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_one": "{{countFormatted}} download option",
"download_count_other": "{{countFormatted}} download options",
@@ -435,9 +437,16 @@
"add_download_source_description": "Insert the URL of the .json file",
"download_source_up_to_date": "Up-to-date",
"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",
"removed_download_source": "Download source 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",
"confirm_button_confirmation_delete_all_sources": "Yes, delete everything",
"title_confirmation_delete_all_sources": "Delete all download sources",
@@ -468,6 +477,7 @@
"seed_after_download_complete": "Seed after download complete",
"show_hidden_achievement_description": "Show hidden achievements description before unlocking them",
"account": "Account",
"hydra_cloud": "Hydra Cloud",
"no_users_blocked": "You have no blocked users",
"subscription_active_until": "Your Hydra Cloud is active until {{date}}",
"manage_subscription": "Manage subscription",

View File

@@ -423,7 +423,13 @@
"add_download_source_description": "Insira a URL contendo o arquivo .json",
"download_source_up_to_date": "Sincronizada",
"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",
"download_sources_synced_successfully": "Fontes de download sincronizadas",
"removed_download_source": "Fonte removida",
"removed_download_sources": "Fontes removidas",
"cancel_button_confirmation_delete_all_sources": "Não",

View File

@@ -252,7 +252,13 @@
"add_download_source_description": "Insere o URL que contém o ficheiro .json",
"download_source_up_to_date": "Sincronizada",
"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",
"download_sources_synced_successfully": "Fontes de download sincronizadas",
"removed_download_source": "Fonte removida",
"cancel_button_confirmation_delete_all_sources": "Não",
"confirm_button_confirmation_delete_all_sources": "Sim, apague tudo",

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Button, Modal, TextField } from "@renderer/components";
import { settingsContext } from "@renderer/context";
import { useForm } from "react-hook-form";
import { logger } from "@renderer/logger";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
@@ -55,10 +56,10 @@ export function AddDownloadSourceModal({
onClose();
onAddDownloadSource();
} catch (error) {
console.error("Failed to add download source:", error);
logger.error("Failed to add download source:", error);
setError("url", {
type: "server",
message: "Failed to add download source. Please try again.",
message: t("failed_add_download_source"),
});
} finally {
setIsLoading(false);

View File

@@ -201,7 +201,7 @@ export function SettingsAccount() {
</section>
<section className="settings-account__section">
<h3>Hydra Cloud</h3>
<h3>{t("hydra_cloud")}</h3>
<div className="settings-account__subscription-info">
{getHydraCloudSectionContent().description}
</div>

View File

@@ -1,5 +1,14 @@
@use "../../scss/globals.scss";
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.settings-download-sources {
&__list {
padding: 0;
@@ -22,6 +31,17 @@
&--syncing {
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 {

View File

@@ -22,6 +22,7 @@ import { settingsContext } from "@renderer/context";
import { useNavigate } from "react-router-dom";
import { setFilters, clearFilters } from "@renderer/features";
import "./settings-download-sources.scss";
import { logger } from "@renderer/logger";
export function SettingsDownloadSources() {
const [
@@ -58,6 +59,30 @@ export function SettingsDownloadSources() {
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) => {
setIsRemovingDownloadSource(true);
@@ -67,7 +92,7 @@ export function SettingsDownloadSources() {
setDownloadSources(sources as DownloadSource[]);
showSuccessToast(t("removed_download_source"));
} catch (error) {
console.error("Failed to remove download source:", error);
logger.error("Failed to remove download source:", error);
} finally {
setIsRemovingDownloadSource(false);
}
@@ -82,7 +107,7 @@ export function SettingsDownloadSources() {
setDownloadSources(sources as DownloadSource[]);
showSuccessToast(t("removed_all_download_sources"));
} catch (error) {
console.error("Failed to remove all download sources:", error);
logger.error("Failed to remove all download sources:", error);
} finally {
setIsRemovingDownloadSource(false);
setShowConfirmationDeleteAllSourcesModal(false);
@@ -94,7 +119,7 @@ export function SettingsDownloadSources() {
const sources = await window.electron.getDownloadSources();
setDownloadSources(sources as DownloadSource[]);
} 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) => {
if (!fingerprint) {
console.error("Cannot navigate: fingerprint is undefined");
logger.error("Cannot navigate: fingerprint is undefined");
return;
}
@@ -202,16 +227,25 @@ export function SettingsDownloadSources() {
<ul className="settings-download-sources__list">
{downloadSources.map((downloadSource) => {
const isPendingOrMatching =
downloadSource.status === DownloadSourceStatus.PendingMatching ||
downloadSource.status === DownloadSourceStatus.Matching;
return (
<li
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">
<h2>{downloadSource.name}</h2>
<div style={{ display: "flex" }}>
<Badge>{statusTitle[downloadSource.status]}</Badge>
<Badge>
{isPendingOrMatching && (
<SyncIcon className="settings-download-sources__spinner" />
)}
{statusTitle[downloadSource.status]}
</Badge>
</div>
<button
@@ -223,11 +257,13 @@ export function SettingsDownloadSources() {
}
>
<small>
{t("download_count", {
count: downloadSource.downloadCount,
countFormatted:
downloadSource.downloadCount.toLocaleString(),
})}
{isPendingOrMatching
? t("download_source_no_information")
: t("download_count", {
count: downloadSource.downloadCount,
countFormatted:
downloadSource.downloadCount.toLocaleString(),
})}
</small>
</button>
</div>

View File

@@ -133,7 +133,7 @@ export function SettingsRealDebrid() {
{t("save_changes")}
</Button>
}
placeholder="API Token"
placeholder={t("api_token")}
hint={
<Trans i18nKey="debrid_api_token_hint" ns="settings">
<Link to={REAL_DEBRID_API_TOKEN_URL} />

View File

@@ -116,7 +116,7 @@ export function SettingsTorBox() {
onChange={(event) =>
setForm({ ...form, torBoxApiToken: event.target.value })
}
placeholder="API Token"
placeholder={t("api_token")}
rightContent={
<Button type="submit" disabled={isButtonDisabled}>
{t("save_changes")}