Compare commits

..

23 Commits

Author SHA1 Message Date
Chubby Granny Chaser
bf8c63aa25 Update package.json 2024-07-05 17:07:37 +01:00
Zamitto
be48306ca2 Merge pull request #768 from hydralauncher/hyd-229-improve-visibility-of-update-message
feat: add color to update icon and notify when update is ready to install
2024-07-05 12:45:53 -03:00
Zamitto
ab81e21341 feat: update font size 2024-07-05 12:38:53 -03:00
Zamitto
b7f94102da feat: add version string to notification 2024-07-05 12:30:34 -03:00
Zamitto
9e7b27afe6 feat: undo change 2024-07-05 12:22:13 -03:00
Zamitto
c24523e8e6 feat: update i18n 2024-07-05 12:18:37 -03:00
Zamitto
b58330ed35 feat: undo change 2024-07-05 12:13:47 -03:00
Zamitto
dde40f39e9 Merge branch 'main' into hyd-229-improve-visibility-of-update-message 2024-07-05 12:10:30 -03:00
Zamitto
d2b3017de9 feat: show notification only when update is ready to install 2024-07-05 12:10:19 -03:00
Chubby Granny Chaser
64f4dad7cc Merge pull request #783 from hydralauncher/fix/replacing-underscore-with-whitespace
feat: replacing underscore with whitespace
2024-07-05 16:04:49 +01:00
Chubby Granny Chaser
154d211b21 Merge branch 'main' into fix/replacing-underscore-with-whitespace 2024-07-05 15:54:42 +01:00
Chubby Granny Chaser
7905ef6c10 feat: replacing underscore with whitespace 2024-07-05 15:53:32 +01:00
Zamitto
b09f2c055f feat: creating notification for update available 2024-07-04 20:00:20 -03:00
Chubby Granny Chaser
2c5b3b4ffa Merge pull request #778 from hydralauncher/feature/adding-directors-cut-filter
Feature/adding directors cut filter
2024-07-04 23:41:07 +01:00
Chubby Granny Chaser
fdefc0c165 feat: adding directors cut filter 2024-07-04 23:14:09 +01:00
Chubby Granny Chaser
47ca2535e3 feat: adding directors cut filter 2024-07-04 23:12:20 +01:00
Chubby Granny Chaser
f706836a43 feat: adding directors cut filter 2024-07-04 23:11:21 +01:00
Chubby Granny Chaser
d8158bb80e Merge branch 'main' of github.com:hydralauncher/hydra into fix/adding-sorting-to-repacks-modal 2024-07-04 18:36:15 +01:00
Chubby Granny Chaser
4e422bdf91 feat: migrating download source validation to worker thread 2024-07-04 18:35:47 +01:00
Zamitto
4be3db8007 feat: add error logs 2024-07-03 18:03:11 -03:00
Zamitto
29b64237ed feat: remove old vbs file 2024-07-03 17:57:03 -03:00
Zamitto
d481164bf3 feat: add color to update icon and notify 2024-07-03 17:44:04 -03:00
Chubby Granny Chaser
dc94a886e6 fix: sorting repacks modal 2024-07-02 17:34:46 +01:00
19 changed files with 123 additions and 118 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "hydralauncher",
"version": "2.0.2",
"version": "2.0.3",
"description": "Hydra",
"main": "./out/main/index.js",
"author": "Los Broxas",

View File

@@ -20,13 +20,7 @@
"home": "Home",
"queued": "{{title}} (Queued)",
"game_has_no_executable": "Game has no executable selected",
"sign_in": "Sign in",
"sort_by": "Sort by",
"latest_added": "Latest added",
"alphabetically": "Alphabetically",
"last_launched": "Last launched",
"number_of_hours": "Number of hours",
"installed_or_not": "Installed or not"
"sign_in": "Sign in"
},
"header": {
"search": "Search games",
@@ -205,7 +199,9 @@
"game_ready_to_install": "{{title}} is ready to install",
"repack_list_updated": "Repack list updated",
"repack_count_one": "{{count}} repack added",
"repack_count_other": "{{count}} repacks added"
"repack_count_other": "{{count}} repacks added",
"new_update_available": "Version {{version}} available",
"restart_to_install_update": "Restart Hydra to install the update"
},
"system_tray": {
"open": "Open Hydra",

View File

@@ -199,7 +199,8 @@
"game_ready_to_install": "{{title}} está listo para instalarse",
"repack_list_updated": "Lista de repacks actualizadas",
"repack_count_one": "{{count}} repack ha sido añadido",
"repack_count_other": "{{count}} repacks añadidos"
"repack_count_other": "{{count}} repacks añadidos",
"new_update_available": "Version {{version}} disponible"
},
"system_tray": {
"open": "Abrir Hydra",

View File

@@ -20,13 +20,7 @@
"home": "Início",
"queued": "{{title}} (Na fila)",
"game_has_no_executable": "Jogo não possui executável selecionado",
"sign_in": "Login",
"sort_by": "Ordenar por",
"latest_added": "Adicionado recente",
"alphabetically": "Alfabeticamente",
"last_launched": "Último jogado",
"number_of_hours": "Qtd. de horas jogadas",
"installed_or_not": "Instalado ou não"
"sign_in": "Login"
},
"header": {
"search": "Buscar jogos",
@@ -201,7 +195,9 @@
"game_ready_to_install": "{{title}} está pronto para ser instalado",
"repack_list_updated": "Lista de repacks atualizada",
"repack_count_one": "{{count}} novo repack",
"repack_count_other": "{{count}} novos repacks"
"repack_count_other": "{{count}} novos repacks",
"new_update_available": "Versão {{version}} disponível",
"restart_to_install_update": "Reinicie o Hydra para instalar a nova versão"
},
"system_tray": {
"open": "Abrir Hydra",

View File

@@ -197,7 +197,8 @@
"game_ready_to_install": "{{title}} готова к установке",
"repack_list_updated": "Список репаков обновлен",
"repack_count_one": "{{count}} репак добавлен",
"repack_count_other": "{{count}} репаков добавлено"
"repack_count_other": "{{count}} репаков добавлено",
"new_update_available": "Доступна версия {{version}}"
},
"system_tray": {
"open": "Открыть Hydra",

View File

@@ -3,6 +3,7 @@ import { registerEvent } from "../register-event";
import updater, { UpdateInfo } from "electron-updater";
import { WindowManager } from "@main/services";
import { app } from "electron";
import { publishNotificationUpdateReadyToInstall } from "@main/services/notifications";
const { autoUpdater } = updater;
@@ -20,13 +21,17 @@ const mockValuesForDebug = () => {
sendEvent({ type: "update-downloaded" });
};
const newVersionInfo = { version: "" };
const checkForUpdates = async (_event: Electron.IpcMainInvokeEvent) => {
autoUpdater
.once("update-available", (info: UpdateInfo) => {
sendEvent({ type: "update-available", info });
newVersionInfo.version = info.version;
})
.once("update-downloaded", () => {
sendEvent({ type: "update-downloaded" });
publishNotificationUpdateReadyToInstall(newVersionInfo.version);
});
if (app.isPackaged) {

View File

@@ -1,17 +1,12 @@
import { registerEvent } from "../register-event";
import axios from "axios";
import { downloadSourceRepository } from "@main/repository";
import { downloadSourceSchema } from "../helpers/validators";
import { RepacksManager } from "@main/services";
import { downloadSourceWorker } from "@main/workers";
const validateDownloadSource = async (
_event: Electron.IpcMainInvokeEvent,
url: string
) => {
const response = await axios.get(url);
const source = downloadSourceSchema.parse(response.data);
const existingSource = await downloadSourceRepository.findOne({
where: { url },
});
@@ -21,14 +16,12 @@ const validateDownloadSource = async (
const repacks = RepacksManager.repacks;
const existingUris = source.downloads
.flatMap((download) => download.uris)
.filter((uri) => repacks.some((repack) => repack.magnet === uri));
return {
name: source.name,
downloadCount: source.downloads.length - existingUris.length,
};
return downloadSourceWorker.run(
{ url, repacks },
{
name: "validateDownloadSource",
}
);
};
registerEvent("validateDownloadSource", validateDownloadSource);

View File

@@ -1,6 +1,18 @@
import { registerEvent } from "../register-event";
import AutoLaunch from "auto-launch";
import { app } from "electron";
import path from "path";
import fs from "node:fs";
import { logger } from "@main/services";
const windowsStartupPath = path.join(
app.getPath("appData"),
"Microsoft",
"Windows",
"Start Menu",
"Programs",
"Startup"
);
const autoLaunch = async (
_event: Electron.IpcMainInvokeEvent,
@@ -13,9 +25,17 @@ const autoLaunch = async (
});
if (enabled) {
appLauncher.enable().catch(() => {});
appLauncher.enable().catch((err) => {
logger.error(err);
});
} else {
appLauncher.disable().catch(() => {});
if (process.platform == "win32") {
fs.rm(path.join(windowsStartupPath, "Hydra.vbs"), () => {});
}
appLauncher.disable().catch((err) => {
logger.error(err);
});
}
};

View File

@@ -18,7 +18,7 @@ import { HydraApi } from "./services/hydra-api";
import { uploadGamesBatch } from "./services/library-sync";
const loadState = async (userPreferences: UserPreferences | null) => {
await RepacksManager.updateRepacks();
RepacksManager.updateRepacks();
import("./events");

View File

@@ -1,7 +1,7 @@
import { Notification, nativeImage } from "electron";
import { t } from "i18next";
import { parseICO } from "icojs";
import trayIcon from "@resources/tray-icon.png?asset";
import { Game } from "@main/entity";
import { gameRepository, userPreferencesRepository } from "@main/repository";
@@ -39,11 +39,9 @@ export const publishDownloadCompleteNotification = async (game: Game) => {
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,
}),
icon,
@@ -60,13 +58,26 @@ export const publishNewRepacksNotifications = async (count: number) => {
new Notification({
title: t("repack_list_updated", {
ns: "notifications",
lng: userPreferences?.language || "en",
}),
body: t("repack_count", {
ns: "notifications",
lng: userPreferences?.language || "en",
count: count,
}),
}).show();
}
};
export const publishNotificationUpdateReadyToInstall = async (
version: string
) => {
new Notification({
title: t("new_update_available", {
ns: "notifications",
version,
}),
body: t("restart_to_install_update", {
ns: "notifications",
}),
icon: trayIcon,
}).show();
};

View File

@@ -1,6 +1,6 @@
import { downloadSourceSchema } from "@main/events/helpers/validators";
import { DownloadSourceStatus } from "@shared";
import type { DownloadSource } from "@types";
import type { DownloadSource, GameRepack } from "@types";
import axios, { AxiosError, AxiosHeaders } from "axios";
import { z } from "zod";
@@ -48,3 +48,24 @@ export const getUpdatedRepacks = async (downloadSources: DownloadSource[]) => {
return results;
};
export const validateDownloadSource = async ({
url,
repacks,
}: {
url: string;
repacks: GameRepack[];
}) => {
const response = await axios.get(url);
const source = downloadSourceSchema.parse(response.data);
const existingUris = source.downloads
.flatMap((download) => download.uris)
.filter((uri) => repacks.some((repack) => repack.magnet === uri));
return {
name: source.name,
downloadCount: source.downloads.length - existingUris.length,
};
};

View File

@@ -47,10 +47,8 @@ export function AutoUpdateSubHeader() {
return (
<header className={styles.subheader}>
<Link to={releasesPageUrl} className={styles.newVersionLink}>
<SyncIcon size={12} />
<small>
<SyncIcon className={styles.newVersionIcon} size={12} />
{t("version_available_download", { version: newVersion })}
</small>
</Link>
</header>
);
@@ -64,10 +62,8 @@ export function AutoUpdateSubHeader() {
className={styles.newVersionButton}
onClick={handleClickInstallUpdate}
>
<SyncIcon size={12} />
<small>
<SyncIcon className={styles.newVersionIcon} size={12} />
{t("version_available_install", { version: newVersion })}
</small>
</button>
</header>
);

View File

@@ -157,7 +157,7 @@ export const newVersionButton = style({
justifyContent: "center",
gap: `${SPACING_UNIT}px`,
color: vars.color.body,
fontSize: "13px",
fontSize: "12px",
":hover": {
textDecoration: "underline",
cursor: "pointer",
@@ -169,5 +169,9 @@ export const newVersionLink = style({
alignItems: "center",
gap: `${SPACING_UNIT}px`,
color: "#8e919b",
fontSize: "13px",
fontSize: "12px",
});
export const newVersionIcon = style({
color: vars.color.success,
});

View File

@@ -7,7 +7,7 @@ export const select = recipe({
base: {
display: "inline-flex",
transition: "all ease 0.2s",
width: "100%",
width: "fit-content",
alignItems: "center",
borderRadius: "8px",
border: `1px solid ${vars.color.border}`,

View File

@@ -34,7 +34,6 @@ export function SelectField({
<select
id={id}
value={value}
style={{width: "100%"}}
className={styles.option}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}

View File

@@ -4,7 +4,7 @@ import { useLocation, useNavigate } from "react-router-dom";
import type { LibraryGame } from "@types";
import { SelectField, TextField } from "@renderer/components";
import { TextField } from "@renderer/components";
import { useDownload, useLibrary, useToast } from "@renderer/hooks";
import { routes } from "./routes";
@@ -34,43 +34,11 @@ export function Sidebar() {
initialSidebarWidth ? Number(initialSidebarWidth) : SIDEBAR_INITIAL_WIDTH
);
const [sortParam, setSortParam] = useState<string>("latest_added");
const sortParamOptions = [
"latest_added",
"alphabetically",
"last_launched",
"number_of_hours",
"installed_or_not",
];
const handleSortParamChange = (e) => {
const selectedOption: string = e.target.value;
setSortParam(selectedOption);
};
const location = useLocation();
const sortedLibrary = useMemo(() => {
switch (sortParam) {
case "latest_added":
return sortBy(library, (game) => game.createdAt);
break;
case "alphabetically":
return sortBy(library, (game) => game.title);
break;
case "last_launched":
return sortBy(library, (game) => game.lastTimePlayed);
break;
case "number_of_hours":
return sortBy(library, (game) => game.playTimeInMilliseconds);
break;
case "installed_or_not":
return sortBy(library, (game) => game.executablePath !== null);
break;
default:
return sortBy(library, (game) => game.title);
}
}, [library, sortParam]);
return sortBy(library, (game) => game.title);
}, [library]);
const { lastPacket, progress } = useDownload();
@@ -225,19 +193,6 @@ export function Sidebar() {
<section className={styles.section}>
<small className={styles.sectionTitle}>{t("my_library")}</small>
<div style={{width: "102%"}}>
<SelectField
label={t("sort_by")}
value={sortParam}
onChange={handleSortParamChange}
options={sortParamOptions.map((option) => ({
key: option,
value: option,
label: t(option),
}))}
/>
</div>
<TextField
placeholder={t("filter")}
onChange={handleFilter}

View File

@@ -1,4 +1,4 @@
import { useCallback, useContext, useEffect, useState } from "react";
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import parseTorrent from "parse-torrent";
@@ -12,6 +12,7 @@ import { format } from "date-fns";
import { DownloadSettingsModal } from "./download-settings-modal";
import { gameDetailsContext } from "@renderer/context";
import { Downloader } from "@shared";
import { orderBy } from "lodash-es";
export interface RepacksModalProps {
visible: boolean;
@@ -38,16 +39,20 @@ export function RepacksModal({
const { t } = useTranslation("game_details");
const sortedRepacks = useMemo(() => {
return orderBy(repacks, (repack) => repack.uploadDate, "desc");
}, [repacks]);
const getInfoHash = useCallback(async () => {
const torrent = await parseTorrent(game?.uri ?? "");
if (torrent.infoHash) setInfoHash(torrent.infoHash);
}, [game]);
useEffect(() => {
setFilteredRepacks(repacks);
setFilteredRepacks(sortedRepacks);
if (game?.uri) getInfoHash();
}, [repacks, visible, game, getInfoHash]);
}, [sortedRepacks, visible, game, getInfoHash]);
const handleRepackClick = (repack: GameRepack) => {
setRepack(repack);
@@ -58,7 +63,7 @@ export function RepacksModal({
const term = event.target.value.toLocaleLowerCase();
setFilteredRepacks(
repacks.filter((repack) => {
sortedRepacks.filter((repack) => {
const lowerCaseTitle = repack.title.toLowerCase();
const lowerCaseRepacker = repack.repacker.toLowerCase();

View File

@@ -121,19 +121,16 @@ export function SettingsGeneral() {
}
/>
<div style={{width: "22%"}}>
<SelectField
label={t("language")}
value={form.language}
onChange={handleLanguageChange}
options={languageOptions.map((language) => ({
key: language.option,
value: language.option,
label: language.nativeName,
}))}
/>
</div>
<SelectField
label={t("language")}
value={form.language}
onChange={handleLanguageChange}
options={languageOptions.map((language) => ({
key: language.option,
value: language.option,
label: language.nativeName,
}))}
/>
<h3>{t("notifications")}</h3>
<>

View File

@@ -51,10 +51,15 @@ export const removeSpecialEditionFromName = (name: string) =>
export const removeDuplicateSpaces = (name: string) =>
name.replace(/\s{2,}/g, " ");
export const replaceUnderscoreWithSpace = (name: string) =>
name.replace(/_/g, " ");
export const formatName = pipe<string>(
removeReleaseYearFromName,
removeSymbolsFromName,
removeSpecialEditionFromName,
replaceUnderscoreWithSpace,
(str) => str.replace(/DIRECTOR'S CUT/g, ""),
removeSymbolsFromName,
removeDuplicateSpaces,
(str) => str.trim()
);