Compare commits

..

5 Commits

Author SHA1 Message Date
Zamitto
c4698d951b Merge pull request #1645 from hydralauncher/feat/create-start-menu-shortcut-on-windows
feat: create start menu shortcut on windows
2025-05-02 11:44:29 -03:00
Zamitto
3fe61ef36b feat: increase default toast duration 2025-05-02 11:40:08 -03:00
Zamitto
9d601fa1c8 chore: bump version 2025-05-02 08:09:29 -03:00
Zamitto
8cf4656d6d feat: update redist script 2025-05-02 08:09:10 -03:00
Zamitto
a922d9a166 feat: create start menu shortcut on Windows 2025-05-02 08:08:58 -03:00
13 changed files with 60 additions and 32 deletions

View File

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

View File

@@ -198,7 +198,8 @@
"download_error_not_cached_on_hydra": "This download is not available on Nimbus.", "download_error_not_cached_on_hydra": "This download is not available on Nimbus.",
"game_removed_from_favorites": "Game removed from favorites", "game_removed_from_favorites": "Game removed from favorites",
"game_added_to_favorites": "Game added to favorites", "game_added_to_favorites": "Game added to favorites",
"automatically_extract_downloaded_files": "Automatically extract downloaded files" "automatically_extract_downloaded_files": "Automatically extract downloaded files",
"create_start_menu_shortcut": "Create Start Menu shortcut"
}, },
"activation": { "activation": {
"title": "Activate Hydra", "title": "Activate Hydra",

View File

@@ -187,7 +187,8 @@
"download_error_not_cached_on_hydra": "Este download não está disponível no Nimbus.", "download_error_not_cached_on_hydra": "Este download não está disponível no Nimbus.",
"game_removed_from_favorites": "Jogo removido dos favoritos", "game_removed_from_favorites": "Jogo removido dos favoritos",
"game_added_to_favorites": "Jogo adicionado aos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos",
"automatically_extract_downloaded_files": "Extrair automaticamente os arquivos baixados" "automatically_extract_downloaded_files": "Extrair automaticamente os arquivos baixados",
"create_start_menu_shortcut": "Criar atalho no Menu Iniciar"
}, },
"activation": { "activation": {
"title": "Ativação", "title": "Ativação",

View File

@@ -178,7 +178,8 @@
"download_error_not_cached_on_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_on_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_on_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_on_torbox": "Este download não está disponível no TorBox e a verificação do status do download não está disponível.",
"game_removed_from_favorites": "Jogo removido dos favoritos", "game_removed_from_favorites": "Jogo removido dos favoritos",
"game_added_to_favorites": "Jogo adicionado aos favoritos" "game_added_to_favorites": "Jogo adicionado aos favoritos",
"create_start_menu_shortcut": "Criar atalho no Menu Iniciar"
}, },
"activation": { "activation": {
"title": "Ativação", "title": "Ativação",

View File

@@ -8,6 +8,14 @@ export const defaultDownloadsPath = SystemPath.getPath("downloads");
export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging");
export const windowsStartMenuPath = path.join(
SystemPath.getPath("appData"),
"Microsoft",
"Windows",
"Start Menu",
"Programs"
);
export const levelDatabasePath = path.join( export const levelDatabasePath = path.join(
SystemPath.getPath("userData"), SystemPath.getPath("userData"),
`hydra-db${isStaging ? "-staging" : ""}` `hydra-db${isStaging ? "-staging" : ""}`

View File

@@ -3,14 +3,16 @@ import createDesktopShortcut from "create-desktop-shortcuts";
import path from "node:path"; import path from "node:path";
import { app } from "electron"; import { app } from "electron";
import { removeSymbolsFromName } from "@shared"; import { removeSymbolsFromName } from "@shared";
import { GameShop } from "@types"; import { GameShop, ShortcutLocation } from "@types";
import { gamesSublevel, levelKeys } from "@main/level"; import { gamesSublevel, levelKeys } from "@main/level";
import { SystemPath } from "@main/services/system-path"; import { SystemPath } from "@main/services/system-path";
import { windowsStartMenuPath } from "@main/constants";
const createGameShortcut = async ( const createGameShortcut = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, shop: GameShop,
objectId: string objectId: string,
location: ShortcutLocation
): Promise<boolean> => { ): Promise<boolean> => {
const gameKey = levelKeys.game(shop, objectId); const gameKey = levelKeys.game(shop, objectId);
const game = await gamesSublevel.get(gameKey); const game = await gamesSublevel.get(gameKey);
@@ -25,7 +27,10 @@ const createGameShortcut = async (
const options = { const options = {
filePath, filePath,
name: removeSymbolsFromName(game.title), name: removeSymbolsFromName(game.title),
outputPath: SystemPath.getPath("desktop"), outputPath:
location === "desktop"
? SystemPath.getPath("desktop")
: windowsStartMenuPath,
}; };
return createDesktopShortcut({ return createDesktopShortcut({

View File

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

View File

@@ -91,7 +91,7 @@ export class CommonRedistManager {
for (const redist of this.redistributables) { for (const redist of this.redistributables) {
const filePath = path.join(commonRedistPath, redist); const filePath = path.join(commonRedistPath, redist);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath) && redist !== "install.bat") {
continue; continue;
} }

View File

@@ -16,6 +16,7 @@ import type {
GameAchievement, GameAchievement,
Theme, Theme,
FriendRequestSync, FriendRequestSync,
ShortcutLocation,
} from "@types"; } from "@types";
import type { AuthPage, CatalogueCategory } from "@shared"; import type { AuthPage, CatalogueCategory } from "@shared";
import type { AxiosProgressEvent } from "axios"; import type { AxiosProgressEvent } from "axios";
@@ -122,8 +123,11 @@ contextBridge.exposeInMainWorld("electron", {
), ),
addGameToLibrary: (shop: GameShop, objectId: string, title: string) => addGameToLibrary: (shop: GameShop, objectId: string, title: string) =>
ipcRenderer.invoke("addGameToLibrary", shop, objectId, title), ipcRenderer.invoke("addGameToLibrary", shop, objectId, title),
createGameShortcut: (shop: GameShop, objectId: string) => createGameShortcut: (
ipcRenderer.invoke("createGameShortcut", shop, objectId), shop: GameShop,
objectId: string,
location: ShortcutLocation
) => ipcRenderer.invoke("createGameShortcut", shop, objectId, location),
updateExecutablePath: ( updateExecutablePath: (
shop: GameShop, shop: GameShop,
objectId: string, objectId: string,

View File

@@ -32,6 +32,7 @@ import type {
Theme, Theme,
Badge, Badge,
Auth, Auth,
ShortcutLocation,
} from "@types"; } from "@types";
import type { AxiosProgressEvent } from "axios"; import type { AxiosProgressEvent } from "axios";
import type disk from "diskusage"; import type disk from "diskusage";
@@ -101,7 +102,11 @@ declare global {
objectId: string, objectId: string,
title: string title: string
) => Promise<void>; ) => Promise<void>;
createGameShortcut: (shop: GameShop, objectId: string) => Promise<boolean>; createGameShortcut: (
shop: GameShop,
objectId: string,
location: ShortcutLocation
) => Promise<boolean>;
updateExecutablePath: ( updateExecutablePath: (
shop: GameShop, shop: GameShop,
objectId: string, objectId: string,

View File

@@ -26,7 +26,7 @@ export const toastSlice = createSlice({
state.title = action.payload.title; state.title = action.payload.title;
state.message = action.payload.message; state.message = action.payload.message;
state.type = action.payload.type; state.type = action.payload.type;
state.duration = action.payload.duration ?? 2000; state.duration = action.payload.duration ?? 3000;
state.visible = true; state.visible = true;
}, },
closeToast: (state) => { closeToast: (state) => {

View File

@@ -1,7 +1,7 @@
import { useContext, useRef, useState } from "react"; import { useContext, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Button, CheckboxField, Modal, TextField } from "@renderer/components"; import { Button, CheckboxField, Modal, TextField } from "@renderer/components";
import type { LibraryGame } from "@types"; import type { LibraryGame, ShortcutLocation } from "@types";
import { gameDetailsContext } from "@renderer/context"; import { gameDetailsContext } from "@renderer/context";
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal"; import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
import { useDownload, useToast, useUserDetails } from "@renderer/hooks"; import { useDownload, useToast, useUserDetails } from "@renderer/hooks";
@@ -107,15 +107,18 @@ export function GameOptionsModal({
} }
}; };
const handleCreateShortcut = async () => { const handleCreateShortcut = async (location: ShortcutLocation) => {
window.electron window.electron
.createGameShortcut(game.shop, game.objectId) .createGameShortcut(game.shop, game.objectId, location)
.then((success) => { .then((success) => {
if (success) { if (success) {
showSuccessToast(t("create_shortcut_success")); showSuccessToast(t("create_shortcut_success"));
} else { } else {
showErrorToast(t("create_shortcut_error")); showErrorToast(t("create_shortcut_error"));
} }
})
.catch(() => {
showErrorToast(t("create_shortcut_error"));
}); });
}; };
@@ -176,6 +179,9 @@ export function GameOptionsModal({
const shouldShowWinePrefixConfiguration = const shouldShowWinePrefixConfiguration =
window.electron.platform === "linux"; window.electron.platform === "linux";
const shouldShowCreateStartMenuShortcut =
window.electron.platform === "win32";
const handleResetAchievements = async () => { const handleResetAchievements = async () => {
setIsDeletingAchievements(true); setIsDeletingAchievements(true);
try { try {
@@ -278,9 +284,20 @@ export function GameOptionsModal({
> >
{t("open_folder")} {t("open_folder")}
</Button> </Button>
<Button onClick={handleCreateShortcut} theme="outline"> <Button
onClick={() => handleCreateShortcut("desktop")}
theme="outline"
>
{t("create_shortcut")} {t("create_shortcut")}
</Button> </Button>
{shouldShowCreateStartMenuShortcut && (
<Button
onClick={() => handleCreateShortcut("start_menu")}
theme="outline"
>
{t("create_start_menu_shortcut")}
</Button>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -1,5 +1,7 @@
export type GameShop = "steam" | "epic"; export type GameShop = "steam" | "epic";
export type ShortcutLocation = "desktop" | "start_menu";
export interface UnlockedAchievement { export interface UnlockedAchievement {
name: string; name: string;
unlockTime: number; unlockTime: number;