mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
feat: moving notifications
This commit is contained in:
parent
d6e57c20c7
commit
ef036d6f57
17 changed files with 179 additions and 87 deletions
|
@ -163,16 +163,20 @@
|
||||||
"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",
|
||||||
"download_options_zero": "No download option",
|
"download_count_zero": "No downloads in list",
|
||||||
"download_options_one": "{{countFormatted}} download option",
|
"download_count_one": "{{countFormatted}} download in list",
|
||||||
"download_options_other": "{{countFormatted}} download options",
|
"download_count_other": "{{countFormatted}} downloads in list",
|
||||||
|
"download_options_zero": "No download available",
|
||||||
|
"download_options_one": "{{countFormatted}} download available",
|
||||||
|
"download_options_other": "{{countFormatted}} downloads available",
|
||||||
"download_source_url": "Download source URL",
|
"download_source_url": "Download source URL",
|
||||||
"add_download_source_description": "Insert the URL containing the JSON file",
|
"add_download_source_description": "Insert the URL containing 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",
|
||||||
"resync_download_sources": "Resync",
|
"sync_download_sources": "Sync sources",
|
||||||
"removed_download_source": "Download source removed",
|
"removed_download_source": "Download source removed",
|
||||||
"added_download_source": "Added download source"
|
"added_download_source": "Added download source",
|
||||||
|
"download_sources_synced": "All download sources are synced"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
|
|
|
@ -145,7 +145,7 @@
|
||||||
"launch_with_system": "Iniciar o Hydra junto com o sistema",
|
"launch_with_system": "Iniciar o Hydra junto com o sistema",
|
||||||
"general": "Geral",
|
"general": "Geral",
|
||||||
"behavior": "Comportamento",
|
"behavior": "Comportamento",
|
||||||
"download_sources": "Bibliotecas de download",
|
"download_sources": "Fontes de download",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"real_debrid_api_token": "Token de API",
|
"real_debrid_api_token": "Token de API",
|
||||||
"enable_real_debrid": "Habilitar Real-Debrid",
|
"enable_real_debrid": "Habilitar Real-Debrid",
|
||||||
|
@ -156,20 +156,24 @@
|
||||||
"real_debrid_linked_message": "Conta \"{{username}}\" vinculada",
|
"real_debrid_linked_message": "Conta \"{{username}}\" vinculada",
|
||||||
"save_changes": "Salvar mudanças",
|
"save_changes": "Salvar mudanças",
|
||||||
"changes_saved": "Ajustes salvos com sucesso",
|
"changes_saved": "Ajustes salvos com sucesso",
|
||||||
"download_sources_description": "Hydra vai buscar links de download em todas as bibliotecas habilitadas. A URL da biblioteca deve ser um link direto para um arquivo .json contendo uma lista de links.",
|
"download_sources_description": "Hydra vai buscar links de download em todas as fonte habilitadas. A URL da fonte deve ser um link direto para um arquivo .json contendo uma lista de links.",
|
||||||
"validate_download_source": "Validar",
|
"validate_download_source": "Validar",
|
||||||
"remove_download_source": "Remover",
|
"remove_download_source": "Remover",
|
||||||
"add_download_source": "Adicionar biblioteca",
|
"add_download_source": "Adicionar fonte",
|
||||||
"download_options_zero": "Sem opções de download",
|
"download_count_zero": "Sem downloads na lista",
|
||||||
"download_options_one": "{{countFormatted}} opcão de download",
|
"download_count_one": "{{countFormatted}} download na lista",
|
||||||
"download_options_other": "{{countFormatted}} opções de download",
|
"download_count_other": "{{countFormatted}} downloads na lista",
|
||||||
"download_source_url": "URL da biblioteca",
|
"download_options_zero": "Sem downloads disponíveis",
|
||||||
"add_download_source_description": "Insira a URL contendo o arquivo JSON",
|
"download_options_one": "{{countFormatted}} download disponível",
|
||||||
"download_source_up_to_date": "Atualizado",
|
"download_options_other": "{{countFormatted}} downloads disponíveis",
|
||||||
|
"download_source_url": "URL da fonte",
|
||||||
|
"add_download_source_description": "Insira a URL contendo o arquivo .json",
|
||||||
|
"download_source_up_to_date": "Sincronizada",
|
||||||
"download_source_errored": "Falhou",
|
"download_source_errored": "Falhou",
|
||||||
"resync_download_sources": "Resincronizar",
|
"sync_download_sources": "Sincronizar",
|
||||||
"removed_download_source": "Biblioteca removida",
|
"removed_download_source": "Fonte removida",
|
||||||
"added_download_source": "Biblioteca adicionada"
|
"added_download_source": "Fonte adicionada",
|
||||||
|
"download_sources_synced": "As fontes foram sincronizadas"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download concluído",
|
"download_complete": "Download concluído",
|
||||||
|
|
|
@ -24,6 +24,9 @@ export class DownloadSource {
|
||||||
@Column("text", { nullable: true })
|
@Column("text", { nullable: true })
|
||||||
etag: string | null;
|
etag: string | null;
|
||||||
|
|
||||||
|
@Column("int", { default: 0 })
|
||||||
|
downloadCount: number;
|
||||||
|
|
||||||
@Column("text", { default: DownloadSourceStatus.UpToDate })
|
@Column("text", { default: DownloadSourceStatus.UpToDate })
|
||||||
status: DownloadSourceStatus;
|
status: DownloadSourceStatus;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,11 @@ const addDownloadSource = async (
|
||||||
async (transactionalEntityManager) => {
|
async (transactionalEntityManager) => {
|
||||||
const downloadSource = await transactionalEntityManager
|
const downloadSource = await transactionalEntityManager
|
||||||
.getRepository(DownloadSource)
|
.getRepository(DownloadSource)
|
||||||
.save({ url, name: source.name });
|
.save({
|
||||||
|
url,
|
||||||
|
name: source.name,
|
||||||
|
downloadCount: source.downloads.length,
|
||||||
|
});
|
||||||
|
|
||||||
await insertDownloadsFromSource(
|
await insertDownloadsFromSource(
|
||||||
transactionalEntityManager,
|
transactionalEntityManager,
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { fetchDownloadSourcesAndUpdate } from "@main/helpers";
|
||||||
|
|
||||||
|
const syncDownloadSources = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||||
|
fetchDownloadSourcesAndUpdate();
|
||||||
|
|
||||||
|
registerEvent("syncDownloadSources", syncDownloadSources);
|
|
@ -34,6 +34,7 @@ import "./download-sources/get-download-sources";
|
||||||
import "./download-sources/validate-download-source";
|
import "./download-sources/validate-download-source";
|
||||||
import "./download-sources/add-download-source";
|
import "./download-sources/add-download-source";
|
||||||
import "./download-sources/remove-download-source";
|
import "./download-sources/remove-download-source";
|
||||||
|
import "./download-sources/sync-download-sources";
|
||||||
|
|
||||||
ipcMain.handle("ping", () => "pong");
|
ipcMain.handle("ping", () => "pong");
|
||||||
ipcMain.handle("getVersion", () => app.getVersion());
|
ipcMain.handle("getVersion", () => app.getVersion());
|
||||||
|
|
|
@ -52,19 +52,22 @@ export const fetchDownloadSourcesAndUpdate = async () => {
|
||||||
|
|
||||||
await dataSource.transaction(async (transactionalEntityManager) => {
|
await dataSource.transaction(async (transactionalEntityManager) => {
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
await transactionalEntityManager.getRepository(DownloadSource).update(
|
if (result.etag !== null) {
|
||||||
{ id: result.id },
|
await transactionalEntityManager.getRepository(DownloadSource).update(
|
||||||
{
|
{ id: result.id },
|
||||||
etag: result.etag,
|
{
|
||||||
status: result.status,
|
etag: result.etag,
|
||||||
}
|
status: result.status,
|
||||||
);
|
downloadCount: result.downloads.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
await insertDownloadsFromSource(
|
await insertDownloadsFromSource(
|
||||||
transactionalEntityManager,
|
transactionalEntityManager,
|
||||||
result,
|
result,
|
||||||
result.downloads
|
result.downloads
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await RepacksManager.updateRepacks();
|
await RepacksManager.updateRepacks();
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import Aria2, { StatusResponse } from "aria2";
|
import Aria2, { StatusResponse } from "aria2";
|
||||||
|
|
||||||
import {
|
import { downloadQueueRepository, gameRepository } from "@main/repository";
|
||||||
downloadQueueRepository,
|
|
||||||
gameRepository,
|
|
||||||
userPreferencesRepository,
|
|
||||||
} from "@main/repository";
|
|
||||||
|
|
||||||
import { WindowManager } from "./window-manager";
|
import { WindowManager } from "./window-manager";
|
||||||
import { RealDebridClient } from "./real-debrid";
|
import { RealDebridClient } from "./real-debrid";
|
||||||
import { Notification } from "electron";
|
|
||||||
import { t } from "i18next";
|
|
||||||
import { Downloader } from "@shared";
|
import { Downloader } from "@shared";
|
||||||
import { DownloadProgress } from "@types";
|
import { DownloadProgress } from "@types";
|
||||||
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
|
||||||
|
@ -18,6 +13,7 @@ import { startAria2 } from "./aria2c";
|
||||||
import { sleep } from "@main/helpers";
|
import { sleep } from "@main/helpers";
|
||||||
import { logger } from "./logger";
|
import { logger } from "./logger";
|
||||||
import type { ChildProcess } from "node:child_process";
|
import type { ChildProcess } from "node:child_process";
|
||||||
|
import { publishDownloadCompleteNotification } from "./notifications";
|
||||||
|
|
||||||
export class DownloadManager {
|
export class DownloadManager {
|
||||||
private static downloads = new Map<number, string>();
|
private static downloads = new Map<number, string>();
|
||||||
|
@ -69,26 +65,6 @@ export class DownloadManager {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async publishNotification() {
|
|
||||||
const userPreferences = await userPreferencesRepository.findOne({
|
|
||||||
where: { id: 1 },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userPreferences?.downloadNotificationsEnabled && this.game) {
|
|
||||||
new Notification({
|
|
||||||
title: t("download_complete", {
|
|
||||||
ns: "notifications",
|
|
||||||
lng: userPreferences.language,
|
|
||||||
}),
|
|
||||||
body: t("game_ready_to_install", {
|
|
||||||
ns: "notifications",
|
|
||||||
lng: userPreferences.language,
|
|
||||||
title: this.game.title,
|
|
||||||
}),
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getFolderName(status: StatusResponse) {
|
private static getFolderName(status: StatusResponse) {
|
||||||
if (status.bittorrent?.info) return status.bittorrent.info.name;
|
if (status.bittorrent?.info) return status.bittorrent.info.name;
|
||||||
return "";
|
return "";
|
||||||
|
@ -222,7 +198,7 @@ export class DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress === 1 && this.game && !isDownloadingMetadata) {
|
if (progress === 1 && this.game && !isDownloadingMetadata) {
|
||||||
await this.publishNotification();
|
await publishDownloadCompleteNotification(this.game);
|
||||||
|
|
||||||
await downloadQueueRepository.delete({ game: this.game });
|
await downloadQueueRepository.delete({ game: this.game });
|
||||||
|
|
||||||
|
|
24
src/main/services/notifications.ts
Normal file
24
src/main/services/notifications.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { Notification } from "electron";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import { Game } from "@main/entity";
|
||||||
|
import { userPreferencesRepository } from "@main/repository";
|
||||||
|
|
||||||
|
export const publishDownloadCompleteNotification = async (game: Game) => {
|
||||||
|
const userPreferences = await userPreferencesRepository.findOne({
|
||||||
|
where: { id: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (userPreferences?.downloadNotificationsEnabled) {
|
||||||
|
new Notification({
|
||||||
|
title: t("download_complete", {
|
||||||
|
ns: "notifications",
|
||||||
|
lng: userPreferences.language,
|
||||||
|
}),
|
||||||
|
body: t("game_ready_to_install", {
|
||||||
|
ns: "notifications",
|
||||||
|
lng: userPreferences.language,
|
||||||
|
title: game.title,
|
||||||
|
}),
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
};
|
|
@ -38,6 +38,7 @@ export const getUpdatedRepacks = async (downloadSources: DownloadSource[]) => {
|
||||||
results.push({
|
results.push({
|
||||||
...downloadSource,
|
...downloadSource,
|
||||||
downloads: [],
|
downloads: [],
|
||||||
|
etag: null,
|
||||||
status: isNotModified
|
status: isNotModified
|
||||||
? DownloadSourceStatus.UpToDate
|
? DownloadSourceStatus.UpToDate
|
||||||
: DownloadSourceStatus.Errored,
|
: DownloadSourceStatus.Errored,
|
||||||
|
|
|
@ -58,6 +58,7 @@ contextBridge.exposeInMainWorld("electron", {
|
||||||
ipcRenderer.invoke("addDownloadSource", url),
|
ipcRenderer.invoke("addDownloadSource", url),
|
||||||
removeDownloadSource: (id: number) =>
|
removeDownloadSource: (id: number) =>
|
||||||
ipcRenderer.invoke("removeDownloadSource", id),
|
ipcRenderer.invoke("removeDownloadSource", id),
|
||||||
|
syncDownloadSources: () => ipcRenderer.invoke("syncDownloadSources"),
|
||||||
|
|
||||||
/* Library */
|
/* Library */
|
||||||
addGameToLibrary: (
|
addGameToLibrary: (
|
||||||
|
|
|
@ -103,7 +103,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||||
isWindows: window.electron.platform === "win32",
|
isWindows: window.electron.platform === "win32",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={styles.section}>
|
<section className={styles.section}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.backButton({
|
className={styles.backButton({
|
||||||
|
@ -122,7 +122,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</section>
|
||||||
|
|
||||||
<section className={styles.section}>
|
<section className={styles.section}>
|
||||||
<div className={styles.search({ focused: isFocused })}>
|
<div className={styles.search({ focused: isFocused })}>
|
||||||
|
|
1
src/renderer/src/declaration.d.ts
vendored
1
src/renderer/src/declaration.d.ts
vendored
|
@ -85,6 +85,7 @@ declare global {
|
||||||
) => Promise<{ name: string; downloadCount: number }>;
|
) => Promise<{ name: string; downloadCount: number }>;
|
||||||
addDownloadSource: (url: string) => Promise<DownloadSource>;
|
addDownloadSource: (url: string) => Promise<DownloadSource>;
|
||||||
removeDownloadSource: (id: number) => Promise<void>;
|
removeDownloadSource: (id: number) => Promise<void>;
|
||||||
|
syncDownloadSources: () => Promise<void>;
|
||||||
|
|
||||||
/* Hardware */
|
/* Hardware */
|
||||||
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
|
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
|
||||||
|
|
|
@ -48,13 +48,16 @@ export function Downloads() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = library.reduce((prev, next) => {
|
const result = library.reduce((prev, next) => {
|
||||||
if (lastPacket?.game.id === next.id) {
|
/* Game has been manually added to the library */
|
||||||
return { ...prev, downloading: [...prev.downloading, next] };
|
if (!next.status) return prev;
|
||||||
}
|
|
||||||
|
|
||||||
if (next.downloadQueue || next.status === "paused") {
|
/* Is downloading */
|
||||||
|
if (lastPacket?.game.id === next.id)
|
||||||
|
return { ...prev, downloading: [...prev.downloading, next] };
|
||||||
|
|
||||||
|
/* Is either queued or paused */
|
||||||
|
if (next.downloadQueue || next.status === "paused")
|
||||||
return { ...prev, queued: [...prev.queued, next] };
|
return { ...prev, queued: [...prev.queued, next] };
|
||||||
}
|
|
||||||
|
|
||||||
return { ...prev, complete: [...prev.complete, next] };
|
return { ...prev, complete: [...prev.complete, next] };
|
||||||
}, initialValue);
|
}, initialValue);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
import { style } from "@vanilla-extract/css";
|
||||||
import { SPACING_UNIT, vars } from "../../theme.css";
|
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||||
|
import { recipe } from "@vanilla-extract/recipes";
|
||||||
|
|
||||||
export const downloadSourceField = style({
|
export const downloadSourceField = style({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -14,14 +15,24 @@ export const downloadSources = style({
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadSourceItem = style({
|
export const downloadSourceItem = recipe({
|
||||||
display: "flex",
|
base: {
|
||||||
flexDirection: "column",
|
display: "flex",
|
||||||
backgroundColor: vars.color.darkBackground,
|
flexDirection: "column",
|
||||||
borderRadius: "8px",
|
backgroundColor: vars.color.darkBackground,
|
||||||
padding: `${SPACING_UNIT * 2}px`,
|
borderRadius: "8px",
|
||||||
gap: `${SPACING_UNIT}px`,
|
padding: `${SPACING_UNIT * 2}px`,
|
||||||
border: `solid 1px ${vars.color.border}`,
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
border: `solid 1px ${vars.color.border}`,
|
||||||
|
transition: "all ease 0.2s",
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
isSyncing: {
|
||||||
|
true: {
|
||||||
|
opacity: vars.opacity.disabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadSourceItemHeader = style({
|
export const downloadSourceItemHeader = style({
|
||||||
|
@ -36,3 +47,10 @@ export const downloadSourcesHeader = style({
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const separator = style({
|
||||||
|
height: "100%",
|
||||||
|
width: "1px",
|
||||||
|
backgroundColor: vars.color.border,
|
||||||
|
margin: `${SPACING_UNIT}px 0`,
|
||||||
|
});
|
||||||
|
|
|
@ -9,11 +9,14 @@ import { NoEntryIcon, PlusCircleIcon, SyncIcon } from "@primer/octicons-react";
|
||||||
import { AddDownloadSourceModal } from "./add-download-source-modal";
|
import { AddDownloadSourceModal } from "./add-download-source-modal";
|
||||||
import { useToast } from "@renderer/hooks";
|
import { useToast } from "@renderer/hooks";
|
||||||
import { DownloadSourceStatus } from "@shared";
|
import { DownloadSourceStatus } from "@shared";
|
||||||
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
|
||||||
export function SettingsDownloadSources() {
|
export function SettingsDownloadSources() {
|
||||||
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
||||||
useState(false);
|
useState(false);
|
||||||
const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]);
|
const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]);
|
||||||
|
const [isSyncingDownloadSources, setIsSyncingDownloadSources] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
@ -41,6 +44,20 @@ export function SettingsDownloadSources() {
|
||||||
showSuccessToast(t("added_download_source"));
|
showSuccessToast(t("added_download_source"));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const syncDownloadSources = async () => {
|
||||||
|
setIsSyncingDownloadSources(true);
|
||||||
|
|
||||||
|
window.electron
|
||||||
|
.syncDownloadSources()
|
||||||
|
.then(() => {
|
||||||
|
showSuccessToast(t("download_sources_synced"));
|
||||||
|
getDownloadSources();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsSyncingDownloadSources(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const statusTitle = {
|
const statusTitle = {
|
||||||
[DownloadSourceStatus.UpToDate]: t("download_source_up_to_date"),
|
[DownloadSourceStatus.UpToDate]: t("download_source_up_to_date"),
|
||||||
[DownloadSourceStatus.Errored]: t("download_source_errored"),
|
[DownloadSourceStatus.Errored]: t("download_source_errored"),
|
||||||
|
@ -62,25 +79,31 @@ export function SettingsDownloadSources() {
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
theme="outline"
|
theme="outline"
|
||||||
onClick={() => setShowAddDownloadSourceModal(true)}
|
disabled={!downloadSources.length || isSyncingDownloadSources}
|
||||||
|
onClick={syncDownloadSources}
|
||||||
>
|
>
|
||||||
<PlusCircleIcon />
|
<SyncIcon />
|
||||||
{t("add_download_source")}
|
{t("sync_download_sources")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
theme="outline"
|
theme="outline"
|
||||||
disabled={!downloadSources.length}
|
onClick={() => setShowAddDownloadSourceModal(true)}
|
||||||
>
|
>
|
||||||
<SyncIcon />
|
<PlusCircleIcon />
|
||||||
{t("resync_download_sources")}
|
{t("add_download_source")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className={styles.downloadSources}>
|
<ul className={styles.downloadSources}>
|
||||||
{downloadSources.map((downloadSource) => (
|
{downloadSources.map((downloadSource) => (
|
||||||
<li key={downloadSource.id} className={styles.downloadSourceItem}>
|
<li
|
||||||
|
key={downloadSource.id}
|
||||||
|
className={styles.downloadSourceItem({
|
||||||
|
isSyncing: isSyncingDownloadSources,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className={styles.downloadSourceItemHeader}>
|
<div className={styles.downloadSourceItemHeader}>
|
||||||
<h2>{downloadSource.name}</h2>
|
<h2>{downloadSource.name}</h2>
|
||||||
|
|
||||||
|
@ -88,12 +111,30 @@ export function SettingsDownloadSources() {
|
||||||
<Badge>{statusTitle[downloadSource.status]}</Badge>
|
<Badge>{statusTitle[downloadSource.status]}</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<small>
|
<div
|
||||||
{t("download_options", {
|
style={{
|
||||||
count: downloadSource.repackCount,
|
display: "flex",
|
||||||
countFormatted: downloadSource.repackCount.toLocaleString(),
|
alignItems: "center",
|
||||||
})}
|
gap: `${SPACING_UNIT}px`,
|
||||||
</small>
|
}}
|
||||||
|
>
|
||||||
|
<small>
|
||||||
|
{t("download_count", {
|
||||||
|
count: downloadSource.downloadCount,
|
||||||
|
countFormatted:
|
||||||
|
downloadSource.downloadCount.toLocaleString(),
|
||||||
|
})}
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<div className={styles.separator} />
|
||||||
|
|
||||||
|
<small>
|
||||||
|
{t("download_options", {
|
||||||
|
count: downloadSource.repackCount,
|
||||||
|
countFormatted: downloadSource.repackCount.toLocaleString(),
|
||||||
|
})}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.downloadSourceField}>
|
<div className={styles.downloadSourceField}>
|
||||||
|
|
|
@ -248,6 +248,7 @@ export interface DownloadSource {
|
||||||
url: string;
|
url: string;
|
||||||
repackCount: number;
|
repackCount: number;
|
||||||
status: DownloadSourceStatus;
|
status: DownloadSourceStatus;
|
||||||
|
downloadCount: number;
|
||||||
etag: string | null;
|
etag: string | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue