feat: favorites

This commit is contained in:
Leandro Perin 2025-02-04 19:05:11 -03:00
parent 70fcc6e2a1
commit 2952d44884
34 changed files with 222 additions and 57 deletions

View file

@ -72,7 +72,8 @@
"change": "Змяніць",
"repacks_modal_description": "Абярыце рэпак, які хочаце сцягнуць",
"select_folder_hint": "Каб змяніць папку па змоўчанні, адкрыйце",
"download_now": "Сцягнуць зараз"
"download_now": "Сцягнуць зараз",
"favorites": "Улюбленыя"
},
"activation": {
"title": "Актываваць Hydra",

View file

@ -176,7 +176,8 @@
"manage_files_description": "Управлявайте кои файлове ще бъдат архивирани и възстановени",
"select_folder": "Избери папка",
"backup_from": "Резервно копие от {{date}}",
"custom_backup_location_set": "Задаване на персонализирано местоположение за архивиране"
"custom_backup_location_set": "Задаване на персонализирано местоположение за архивиране",
"favorites": "Любими игри"
},
"activation": {
"title": "Активирай Hydra",

View file

@ -113,7 +113,8 @@
"download_paused": "Descàrrega en pausa",
"last_downloaded_option": "Opció de l'última descàrrega",
"create_shortcut_success": "Accés directe creat satisfactòriament",
"create_shortcut_error": "Error al crear l'accés directe"
"create_shortcut_error": "Error al crear l'accés directe",
"favorites": "Favorits"
},
"activation": {
"title": "Activa l'Hydra",

View file

@ -166,7 +166,8 @@
"manage_files_description": "Spravovat, které soubory budou zálohovány a obnoveny",
"select_folder": "Vybrat složku",
"backup_from": "Zálohy z {{date}}",
"custom_backup_location_set": "Vlastní umístění záloh nastaveno"
"custom_backup_location_set": "Vlastní umístění záloh nastaveno",
"favorites": "Oblíbené"
},
"activation": {
"title": "Aktivovat hydru",

View file

@ -129,7 +129,8 @@
"download": "Download",
"executable_path_in_use": "Eksekverbar allerede i brug af \"{{game}}\"",
"warning": "Advarsel:",
"hydra_needs_to_remain_open": "Hydra skal forblive åbent for at denne download kan gennemføres. I tilfælde af at Hydra lukker før downloaden er færdig, mister du dit fremskridt."
"hydra_needs_to_remain_open": "Hydra skal forblive åbent for at denne download kan gennemføres. I tilfælde af at Hydra lukker før downloaden er færdig, mister du dit fremskridt.",
"favorites": "Favoriter"
},
"activation": {
"title": "Aktivér Hydra",

View file

@ -113,7 +113,8 @@
"download_paused": "Download ist pausiert",
"last_downloaded_option": "Letzte Download-Option",
"create_shortcut_success": "Verknüpfung erfolgreich erstellt",
"create_shortcut_error": "Fehler bei Erstellung von Verknüpfung"
"create_shortcut_error": "Fehler bei Erstellung von Verknüpfung",
"favorites": "Favoriten"
},
"activation": {
"title": "Hydra aktivieren",

View file

@ -188,8 +188,10 @@
"download_error_gofile_quota_exceeded": "You have exceeded your Gofile monthly quota. Please await the quota to reset.",
"download_error_real_debrid_account_not_authorized": "Your Real-Debrid account is not authorized to make new downloads. Please check your account settings and try again.",
"download_error_not_cached_in_real_debrid": "This download is not available on Real-Debrid and polling download status from Real-Debrid is not yet available.",
"download_error_not_cached_in_torbox": "This download is not available on Torbox and polling download status from Torbox is not yet available."
"download_error_not_cached_in_torbox": "This download is not available on Torbox and polling download status from Torbox is not yet available.",
"favorites": "Favorites"
},
"activation": {
"title": "Activate Hydra",
"installation_id": "Installation ID:",

View file

@ -184,7 +184,8 @@
"reset_achievements_description": "Esto reiniciará todos los logros de {{game}}",
"reset_achievements_title": "¿Estás seguro?",
"reset_achievements_success": "Logros reiniciados exitosamente",
"reset_achievements_error": "Se produjo un error al reiniciar los logros"
"reset_achievements_error": "Se produjo un error al reiniciar los logros",
"favorites": "Favorites"
},
"activation": {
"title": "Activar Hydra",

View file

@ -165,7 +165,8 @@
"manage_files_description": "Hallake, millised failid varundatakse ja taastatakse",
"select_folder": "Vali kaust",
"backup_from": "Varundamine kuupäevast {{date}}",
"custom_backup_location_set": "Kohandatud varundamise asukoht määratud"
"custom_backup_location_set": "Kohandatud varundamise asukoht määratud",
"favorites": "Töölist"
},
"activation": {
"title": "Aktiveeri Hydra",

View file

@ -72,7 +72,8 @@
"change": "تغییر",
"repacks_modal_description": "ریپک مورد نظر برای دانلود را انتخاب کنید",
"select_folder_hint": "برای تغییر پوشه‌ی پیش‌فرض به <0>Settings</0> بروید",
"download_now": "الان دانلود کن"
"download_now": "الان دانلود کن",
"favorites": "علاقه‌مندی‌ها"
},
"activation": {
"title": "فعال کردن هایدرا",

View file

@ -64,10 +64,12 @@
"close": "Fermer",
"deleting": "Suppression du programme d'installation…",
"playing_now": "Jeu en cours",
"last_time_played": "Dernièrement joué {{period}}"
"last_time_played": "Dernièrement joué {{period}}",
"favorites": "Favoris"
},
"activation": {
"title": "Activer Hydra",
"installation_id": "ID d'installation :",
"enter_activation_code": "Entrez votre code d'activation",
"message": "Si vous ne savez pas où demander ceci, vous ne devriez pas l'avoir.",

View file

@ -72,7 +72,8 @@
"change": "Változtatás",
"repacks_modal_description": "Choose the repack you want to download",
"select_folder_hint": "Ahhoz, hogy megváltoztasd a helyet, hozzákell férned a",
"download_now": "Töltsd le most"
"download_now": "Töltsd le most",
"favorites": "Kedvenc játékok"
},
"activation": {
"title": "Hydra Aktiválása",

View file

@ -113,7 +113,8 @@
"download_paused": "Unduhan dijeda",
"last_downloaded_option": "Opsi terakhir diunduh",
"create_shortcut_success": "Pintasan berhasil dibuat",
"create_shortcut_error": "Gagal membuat pintasan"
"create_shortcut_error": "Gagal membuat pintasan",
"favorites": "Favorit"
},
"activation": {
"title": "Aktifkan Hydra",

View file

@ -79,7 +79,8 @@
"previous_screenshot": "Screenshot precedente",
"next_screenshot": "Screenshot successivo",
"screenshot": "Screenshot {{number}}",
"open_screenshot": "Apri screenshot {{number}}"
"open_screenshot": "Apri screenshot {{number}}",
"favorites": "Preferiti"
},
"activation": {
"title": "Attiva Hydra",

View file

@ -112,7 +112,8 @@
"download_paused": "Жүктеу тоқтатылды",
"last_downloaded_option": "Соңғы жүктеу нұсқасы",
"create_shortcut_success": "Жарлық жасалды",
"create_shortcut_error": "Жарлық жасау мүмкін болмады"
"create_shortcut_error": "Жарлық жасау мүмкін болмады",
"favorites": "Қолданған"
},
"activation": {
"title": "Hydra-ны белсендіру",

View file

@ -72,7 +72,8 @@
"change": "바꾸기",
"repacks_modal_description": "다운로드 할 리팩을 선택해 주세요",
"select_folder_hint": "기본 폴더를 바꾸려면 <0>설정</0>으로 가세요",
"download_now": "지금 다운로드"
"download_now": "지금 다운로드",
"favorites": "즐겨찾기"
},
"activation": {
"title": "Hydra 실행",

View file

@ -129,7 +129,8 @@
"download": "Last ned",
"executable_path_in_use": "Kjørbar fil blir allerede brukt av \"{{game}}\"",
"warning": "Advarsel:",
"hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt."
"hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt.",
"favorites": "Favoritter"
},
"activation": {
"title": "Aktivér Hydra",

View file

@ -72,7 +72,8 @@
"change": "Verander",
"repacks_modal_description": "Kies de herverpakking die u wilt downloaden",
"select_folder_hint": "Om de standaardmap te wijzigen, gaat u naar <0>instellingen</0>",
"download_now": "Download nu"
"download_now": "Download nu",
"favorites": "Favorieten"
},
"activation": {
"title": "Activeer Hydra",

View file

@ -79,7 +79,8 @@
"previous_screenshot": "Poprzedni zrzut ekranu",
"next_screenshot": "Następny zrzut ekranu",
"screenshot": "Zrzut ekranu {{number}}",
"open_screenshot": "Otwórz zrzut ekranu {{number}}"
"open_screenshot": "Otwórz zrzut ekranu {{number}}",
"favorites": "Ulubione"
},
"activation": {
"title": "Aktywuj Hydra",

View file

@ -177,8 +177,10 @@
"download_error_gofile_quota_exceeded": "Você excedeu sua cota mensal do Gofile. Por favor, aguarde a cota resetar.",
"download_error_real_debrid_account_not_authorized": "Sua conta do Real-Debrid não está autorizada a fazer novos downloads. Por favor, verifique sua assinatura e tente novamente.",
"download_error_not_cached_in_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.",
"download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível."
"download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível.",
"favorites": "Favoritos"
},
"activation": {
"title": "Ativação",
"installation_id": "ID da instalação:",

View file

@ -157,8 +157,10 @@
"no_download_option_info": "Sem informações disponíveis",
"backup_deletion_failed": "Falha ao apagar o backup",
"max_number_of_artifacts_reached": "Número máximo de backups atingido para este jogo",
"achievements_not_sync": "As tuas conquistas não estão sincronizadas"
"achievements_not_sync": "As tuas conquistas não estão sincronizadas",
"favorites": "Favoritos"
},
"activation": {
"title": "Ativação",
"installation_id": "ID da instalação:",

View file

@ -84,7 +84,8 @@
"screenshot": "Captură de ecran {{number}}",
"open_screenshot": "Deschide captura de ecran {{number}}",
"download_settings": "Setări de descărcare",
"downloader": "Program de descărcare"
"downloader": "Program de descărcare",
"favorites": "Favorite"
},
"activation": {
"title": "Activează Hydra",

View file

@ -182,7 +182,8 @@
"no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.",
"reset_achievements_title": "Вы уверены?",
"reset_achievements_success": "Достижения успешно сброшены",
"reset_achievements_error": "Не удалось сбросить достижения"
"reset_achievements_error": "Не удалось сбросить достижения",
"favorites": "Избранное"
},
"activation": {
"title": "Активировать Hydra",

View file

@ -184,7 +184,8 @@
"reset_achievements_description": "Bu işlem {{game}} için tüm başarıları sıfırlar",
"reset_achievements_title": "Emin misiniz?",
"reset_achievements_success": "Başarılar başarıyla sıfırlandı",
"reset_achievements_error": "Başarılar sıfırlanamadı"
"reset_achievements_error": "Başarılar sıfırlanamadı",
"favorites": "Favoriler"
},
"activation": {
"title": "Hydra'yı Aktive Et",

View file

@ -110,7 +110,8 @@
"remove_from_library_description": "{{game}} буде видалено з вашої бібліотеки",
"remove_from_library_title": "Ви впевнені?",
"screenshot": "Скріншот",
"select_executable": "Обрати"
"select_executable": "Обрати",
"favorites": "Улюблені"
},
"activation": {
"title": "Активувати Hydra",

View file

@ -165,7 +165,8 @@
"manage_files_description": "管理哪些文件要备份和恢复",
"select_folder": "选择文件夹",
"backup_from": "{{date}} 时备份",
"custom_backup_location_set": "自定义备份文件位置"
"custom_backup_location_set": "自定义备份文件位置",
"favorites": "收藏"
},
"activation": {
"title": "激活 Hydra",

View file

@ -13,6 +13,8 @@ import "./catalogue/get-developers";
import "./hardware/get-disk-free-space";
import "./hardware/check-folder-write-permission";
import "./library/add-game-to-library";
import "./library/add-game-to-favorites";
import "./library/remove-game-from-favorites";
import "./library/create-game-shortcut";
import "./library/close-game";
import "./library/delete-game-folder";

View file

@ -0,0 +1,21 @@
import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const addGameToFavorites = async (
_event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string
) => {
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
favorite: true,
});
};
registerEvent("addGameToFavorites", addGameToFavorites);

View file

@ -0,0 +1,21 @@
import { registerEvent } from "../register-event";
import { gamesSublevel, levelKeys } from "@main/level";
import type { GameShop } from "@types";
const removeGameFromFavorites = async (
_event: Electron.IpcMainInvokeEvent,
shop: GameShop,
objectId: string
) => {
const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey);
if (!game) return;
await gamesSublevel.put(gameKey, {
...game,
favorite: false,
});
};
registerEvent("removeGameFromFavorites", removeGameFromFavorites);

View file

@ -110,11 +110,16 @@ contextBridge.exposeInMainWorld("electron", {
executablePath: string | null
) =>
ipcRenderer.invoke("updateExecutablePath", shop, objectId, executablePath),
addGameToFavorites: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("addGameToFavorites", shop, objectId),
removeGameFromFavorites: (shop: GameShop, objectId: string) =>
ipcRenderer.invoke("removeGameFromFavorites", shop, objectId),
updateLaunchOptions: (
shop: GameShop,
objectId: string,
launchOptions: string | null
) => ipcRenderer.invoke("updateLaunchOptions", shop, objectId, launchOptions),
selectGameWinePrefix: (
shop: GameShop,
objectId: string,

View file

@ -206,6 +206,48 @@ export function Sidebar() {
</ul>
</section>
<section className="sidebar__section">
<small className="sidebar__section-title">{t("favorites")}</small>
<ul className="sidebar__menu">
{sortedLibrary
.filter((game) => game.favorite)
.map((game) => (
<li
key={game.id}
className={cn("sidebar__menu-item", {
"sidebar__menu-item--active":
location.pathname ===
`/game/${game.shop}/${game.objectId}`,
"sidebar__menu-item--muted":
game.download?.status === "removed",
})}
>
<button
type="button"
className="sidebar__menu-item-button"
onClick={(event) => handleSidebarGameClick(event, game)}
>
{game.iconUrl ? (
<img
className="sidebar__game-icon"
src={game.iconUrl}
alt={game.title}
loading="lazy"
/>
) : (
<SteamLogo className="sidebar__game-icon" />
)}
<span className="sidebar__menu-item-button-label">
{getGameTitle(game)}
</span>
</button>
</li>
))}
</ul>
</section>
<section className="sidebar__section">
<small className="sidebar__section-title">{t("my_library")}</small>
@ -217,39 +259,41 @@ export function Sidebar() {
/>
<ul className="sidebar__menu">
{filteredLibrary.map((game) => (
<li
key={game.id}
className={cn("sidebar__menu-item", {
"sidebar__menu-item--active":
location.pathname ===
`/game/${game.shop}/${game.objectId}`,
"sidebar__menu-item--muted":
game.download?.status === "removed",
})}
>
<button
type="button"
className="sidebar__menu-item-button"
onClick={(event) => handleSidebarGameClick(event, game)}
{filteredLibrary
.filter((game) => !game.favorite)
.map((game) => (
<li
key={game.id}
className={cn("sidebar__menu-item", {
"sidebar__menu-item--active":
location.pathname ===
`/game/${game.shop}/${game.objectId}`,
"sidebar__menu-item--muted":
game.download?.status === "removed",
})}
>
{game.iconUrl ? (
<img
className="sidebar__game-icon"
src={game.iconUrl}
alt={game.title}
loading="lazy"
/>
) : (
<SteamLogo className="sidebar__game-icon" />
)}
<button
type="button"
className="sidebar__menu-item-button"
onClick={(event) => handleSidebarGameClick(event, game)}
>
{game.iconUrl ? (
<img
className="sidebar__game-icon"
src={game.iconUrl}
alt={game.title}
loading="lazy"
/>
) : (
<SteamLogo className="sidebar__game-icon" />
)}
<span className="sidebar__menu-item-button-label">
{getGameTitle(game)}
</span>
</button>
</li>
))}
<span className="sidebar__menu-item-button-label">
{getGameTitle(game)}
</span>
</button>
</li>
))}
</ul>
</section>
</div>

View file

@ -96,6 +96,11 @@ declare global {
objectId: string,
executablePath: string | null
) => Promise<void>;
addGameToFavorites: (shop: GameShop, objectId: string) => Promise<void>;
removeGameFromFavorites: (
shop: GameShop,
objectId: string
) => Promise<void>;
updateLaunchOptions: (
shop: GameShop,
objectId: string,

View file

@ -1,6 +1,8 @@
import {
DownloadIcon,
GearIcon,
HeartFillIcon,
HeartIcon,
PlayIcon,
PlusCircleIcon,
} from "@primer/octicons-react";
@ -52,6 +54,30 @@ export function HeroPanelActions() {
}
};
const addGameToFavorites = async () => {
setToggleLibraryGameDisabled(true);
try {
await window.electron.addGameToFavorites(shop, objectId!);
updateLibrary();
updateGame();
} finally {
setToggleLibraryGameDisabled(false);
}
};
const removeGameFromFavorites = async () => {
setToggleLibraryGameDisabled(true);
try {
await window.electron.removeGameFromFavorites(shop, objectId!);
updateLibrary();
updateGame();
} finally {
setToggleLibraryGameDisabled(false);
}
};
const openGame = async () => {
if (game) {
if (game.executablePath) {
@ -159,6 +185,16 @@ export function HeroPanelActions() {
<div className="hero-panel-actions__container">
{gameActionButton()}
<div className="hero-panel-actions__separator" />
<Button
onClick={game.favorite ? removeGameFromFavorites : addGameToFavorites}
theme="outline"
disabled={deleting}
className="hero-panel-actions__action"
>
{game.favorite ? <HeartFillIcon /> : <HeartIcon />}
</Button>
<Button
onClick={() => setShowGameOptionsModal(true)}
theme="outline"

View file

@ -42,6 +42,7 @@ export interface Game {
winePrefixPath?: string | null;
executablePath?: string | null;
launchOptions?: string | null;
favorite?: boolean;
}
export interface Download {