mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 05:24:55 +03:00
feat: adding translation for button
This commit is contained in:
parent
cbbe6993bd
commit
a121ef77c0
@ -47,13 +47,13 @@
|
||||
"auto-launch": "^5.0.6",
|
||||
"axios": "^1.7.9",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"check-disk-space": "^3.4.0",
|
||||
"classnames": "^2.5.1",
|
||||
"color": "^4.2.3",
|
||||
"color.js": "^1.2.0",
|
||||
"create-desktop-shortcuts": "^1.11.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dexie": "^4.0.10",
|
||||
"diskusage": "^1.2.0",
|
||||
"electron-log": "^5.2.4",
|
||||
"electron-updater": "^6.3.9",
|
||||
"file-type": "^19.6.0",
|
||||
|
@ -178,7 +178,8 @@
|
||||
"select_folder": "Select folder",
|
||||
"backup_from": "Backup from {{date}}",
|
||||
"custom_backup_location_set": "Custom backup location set",
|
||||
"no_directory_selected": "No directory selected"
|
||||
"no_directory_selected": "No directory selected",
|
||||
"no_write_permission": "Cannot download into this directory. Click here to learn more."
|
||||
},
|
||||
"activation": {
|
||||
"title": "Activate Hydra",
|
||||
|
@ -167,7 +167,8 @@
|
||||
"select_folder": "Selecione a pasta",
|
||||
"manage_files_description": "Gerencie quais arquivos serão feitos backup",
|
||||
"clear": "Limpar",
|
||||
"no_directory_selected": "Nenhum diretório selecionado"
|
||||
"no_directory_selected": "Nenhum diretório selecionado",
|
||||
"no_write_permission": "Его нельзя загрузить из этого каталога. Нажмите здесь, чтобы узнать больше."
|
||||
},
|
||||
"activation": {
|
||||
"title": "Ativação",
|
||||
|
15
src/main/events/hardware/check-folder-write-permission.ts
Normal file
15
src/main/events/hardware/check-folder-write-permission.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import fs from "node:fs";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const checkFolderWritePermission = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
path: string
|
||||
) =>
|
||||
new Promise((resolve) => {
|
||||
fs.access(path, fs.constants.W_OK, (err) => {
|
||||
resolve(!err);
|
||||
});
|
||||
});
|
||||
|
||||
registerEvent("checkFolderWritePermission", checkFolderWritePermission);
|
@ -1,10 +1,10 @@
|
||||
import checkDiskSpace from "check-disk-space";
|
||||
import disk from "diskusage";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const getDiskFreeSpace = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
path: string
|
||||
) => checkDiskSpace(path);
|
||||
) => disk.check(path);
|
||||
|
||||
registerEvent("getDiskFreeSpace", getDiskFreeSpace);
|
||||
|
@ -11,6 +11,7 @@ import "./catalogue/get-trending-games";
|
||||
import "./catalogue/get-publishers";
|
||||
import "./catalogue/get-developers";
|
||||
import "./hardware/get-disk-free-space";
|
||||
import "./hardware/check-folder-write-permission";
|
||||
import "./library/add-game-to-library";
|
||||
import "./library/create-game-shortcut";
|
||||
import "./library/close-game";
|
||||
@ -30,6 +31,8 @@ import "./library/select-game-wine-prefix";
|
||||
import "./misc/open-checkout";
|
||||
import "./misc/open-external";
|
||||
import "./misc/show-open-dialog";
|
||||
import "./misc/get-features";
|
||||
import "./misc/show-item-in-folder";
|
||||
import "./torrenting/cancel-game-download";
|
||||
import "./torrenting/pause-game-download";
|
||||
import "./torrenting/resume-game-download";
|
||||
@ -71,7 +74,6 @@ import "./cloud-save/delete-game-artifact";
|
||||
import "./cloud-save/select-game-backup-path";
|
||||
import "./notifications/publish-new-repacks-notification";
|
||||
import { isPortableVersion } from "@main/helpers";
|
||||
import "./misc/show-item-in-folder";
|
||||
|
||||
ipcMain.handle("ping", () => "pong");
|
||||
ipcMain.handle("getVersion", () => appVersion);
|
||||
|
8
src/main/events/misc/get-features.ts
Normal file
8
src/main/events/misc/get-features.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
|
||||
const getFeatures = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
return HydraApi.get<string[]>("/features", null, { needsAuth: false });
|
||||
};
|
||||
|
||||
registerEvent("getFeatures", getFeatures);
|
@ -64,7 +64,10 @@ export class WindowManager {
|
||||
|
||||
this.mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
(details, callback) => {
|
||||
if (details.webContentsId !== this.mainWindow?.webContents.id) {
|
||||
if (
|
||||
details.webContentsId !== this.mainWindow?.webContents.id ||
|
||||
details.url.includes("chatwoot")
|
||||
) {
|
||||
return callback(details);
|
||||
}
|
||||
|
||||
@ -81,15 +84,11 @@ export class WindowManager {
|
||||
|
||||
this.mainWindow.webContents.session.webRequest.onHeadersReceived(
|
||||
(details, callback) => {
|
||||
if (details.webContentsId !== this.mainWindow?.webContents.id) {
|
||||
return callback(details);
|
||||
}
|
||||
|
||||
if (details.url.includes("featurebase")) {
|
||||
return callback(details);
|
||||
}
|
||||
|
||||
if (details.url.includes("chatwoot")) {
|
||||
if (
|
||||
details.webContentsId !== this.mainWindow?.webContents.id ||
|
||||
details.url.includes("featurebase") ||
|
||||
details.url.includes("chatwoot")
|
||||
) {
|
||||
return callback(details);
|
||||
}
|
||||
|
||||
|
@ -150,6 +150,8 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
/* Hardware */
|
||||
getDiskFreeSpace: (path: string) =>
|
||||
ipcRenderer.invoke("getDiskFreeSpace", path),
|
||||
checkFolderWritePermission: (path: string) =>
|
||||
ipcRenderer.invoke("checkFolderWritePermission", path),
|
||||
|
||||
/* Cloud save */
|
||||
uploadSaveGame: (
|
||||
@ -226,6 +228,7 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
ipcRenderer.invoke("showOpenDialog", options),
|
||||
showItemInFolder: (path: string) =>
|
||||
ipcRenderer.invoke("showItemInFolder", path),
|
||||
getFeatures: () => ipcRenderer.invoke("getFeatures"),
|
||||
platform: process.platform,
|
||||
|
||||
/* Auto update */
|
||||
|
@ -46,6 +46,12 @@ export function Modal({
|
||||
}, [onClose]);
|
||||
|
||||
const isTopMostModal = () => {
|
||||
if (
|
||||
document.querySelector(
|
||||
".featurebase-widget-overlay.featurebase-display-block"
|
||||
)
|
||||
)
|
||||
return false;
|
||||
const openModals = document.querySelectorAll("[role=dialog]");
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { useId, useMemo, useState } from "react";
|
||||
import type { RecipeVariants } from "@vanilla-extract/recipes";
|
||||
import type { FieldError, FieldErrorsImpl, Merge } from "react-hook-form";
|
||||
import { EyeClosedIcon, EyeIcon } from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@ -23,7 +22,7 @@ export interface TextFieldProps
|
||||
HTMLDivElement
|
||||
>;
|
||||
rightContent?: React.ReactNode | null;
|
||||
error?: FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined;
|
||||
error?: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
|
||||
@ -55,10 +54,7 @@ export const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
|
||||
}, [props.type, isPasswordVisible]);
|
||||
|
||||
const hintContent = useMemo(() => {
|
||||
if (error && error.message)
|
||||
return (
|
||||
<small className={styles.errorLabel}>{error.message as string}</small>
|
||||
);
|
||||
if (error) return <small className={styles.errorLabel}>{error}</small>;
|
||||
|
||||
if (hint) return <small>{hint}</small>;
|
||||
return null;
|
||||
|
6
src/renderer/src/declaration.d.ts
vendored
6
src/renderer/src/declaration.d.ts
vendored
@ -31,7 +31,7 @@ import type {
|
||||
CatalogueSearchPayload,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type { DiskSpace } from "check-disk-space";
|
||||
import type disk from "diskusage";
|
||||
|
||||
declare global {
|
||||
declare module "*.svg" {
|
||||
@ -140,7 +140,8 @@ declare global {
|
||||
) => Promise<{ fingerprint: string }>;
|
||||
|
||||
/* Hardware */
|
||||
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
|
||||
getDiskFreeSpace: (path: string) => Promise<disk.DiskUsage>;
|
||||
checkFolderWritePermission: (path: string) => Promise<boolean>;
|
||||
|
||||
/* Cloud save */
|
||||
uploadSaveGame: (
|
||||
@ -195,6 +196,7 @@ declare global {
|
||||
options: Electron.OpenDialogOptions
|
||||
) => Promise<Electron.OpenDialogReturnValue>;
|
||||
showItemInFolder: (path: string) => Promise<void>;
|
||||
getFeatures: () => Promise<string[]>;
|
||||
platform: NodeJS.Platform;
|
||||
|
||||
/* Auto update */
|
||||
|
@ -6,3 +6,4 @@ export * from "./redux";
|
||||
export * from "./use-user-details";
|
||||
export * from "./use-format";
|
||||
export * from "./use-repacks";
|
||||
export * from "./use-feature";
|
||||
|
23
src/renderer/src/hooks/use-feature.ts
Normal file
23
src/renderer/src/hooks/use-feature.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
enum Feature {
|
||||
CheckDownloadWritePermission = "CHECK_DOWNLOAD_WRITE_PERMISSION",
|
||||
}
|
||||
|
||||
export function useFeature() {
|
||||
useEffect(() => {
|
||||
window.electron.getFeatures().then((features) => {
|
||||
localStorage.setItem("features", JSON.stringify(features || []));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const isFeatureEnabled = (feature: Feature) => {
|
||||
const features = JSON.parse(localStorage.getItem("features") || "[]");
|
||||
return features.includes(feature);
|
||||
};
|
||||
|
||||
return {
|
||||
isFeatureEnabled,
|
||||
Feature,
|
||||
};
|
||||
}
|
@ -13,6 +13,7 @@ import type {
|
||||
UpdateProfileRequest,
|
||||
UserDetails,
|
||||
} from "@types";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
|
||||
import { isFuture, isToday } from "date-fns";
|
||||
|
||||
@ -44,6 +45,12 @@ export function useUserDetails() {
|
||||
|
||||
const updateUserDetails = useCallback(
|
||||
async (userDetails: UserDetails) => {
|
||||
Sentry.setUser({
|
||||
id: userDetails.id,
|
||||
username: userDetails.username,
|
||||
email: userDetails.email ?? undefined,
|
||||
});
|
||||
|
||||
dispatch(setUserDetails(userDetails));
|
||||
window.localStorage.setItem("userDetails", JSON.stringify(userDetails));
|
||||
},
|
||||
|
@ -36,3 +36,10 @@ export const downloaderIcon = style({
|
||||
position: "absolute",
|
||||
left: `${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
||||
export const pathError = style({
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { DiskSpace } from "check-disk-space";
|
||||
import * as styles from "./download-settings-modal.css";
|
||||
import { Button, Link, Modal, TextField } from "@renderer/components";
|
||||
import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react";
|
||||
@ -10,7 +9,7 @@ import { Downloader, formatBytes, getDownloadersForUris } from "@shared";
|
||||
import type { GameRepack } from "@types";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
import { DOWNLOADER_NAME } from "@renderer/constants";
|
||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||
import { useAppSelector, useFeature, useToast } from "@renderer/hooks";
|
||||
|
||||
export interface DownloadSettingsModalProps {
|
||||
visible: boolean;
|
||||
@ -33,21 +32,45 @@ export function DownloadSettingsModal({
|
||||
|
||||
const { showErrorToast } = useToast();
|
||||
|
||||
const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace | null>(null);
|
||||
const [diskFreeSpace, setDiskFreeSpace] = useState<number | null>(null);
|
||||
const [selectedPath, setSelectedPath] = useState("");
|
||||
const [downloadStarting, setDownloadStarting] = useState(false);
|
||||
const [selectedDownloader, setSelectedDownloader] =
|
||||
useState<Downloader | null>(null);
|
||||
const [hasWritePermission, setHasWritePermission] = useState<boolean | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const { isFeatureEnabled, Feature } = useFeature();
|
||||
|
||||
const userPreferences = useAppSelector(
|
||||
(state) => state.userPreferences.value
|
||||
);
|
||||
|
||||
const getDiskFreeSpace = (path: string) => {
|
||||
window.electron.getDiskFreeSpace(path).then((result) => {
|
||||
setDiskFreeSpace(result.free);
|
||||
});
|
||||
};
|
||||
|
||||
const checkFolderWritePermission = useCallback(
|
||||
async (path: string) => {
|
||||
if (isFeatureEnabled(Feature.CheckDownloadWritePermission)) {
|
||||
const result = await window.electron.checkFolderWritePermission(path);
|
||||
setHasWritePermission(result);
|
||||
} else {
|
||||
setHasWritePermission(true);
|
||||
}
|
||||
},
|
||||
[Feature, isFeatureEnabled]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
getDiskFreeSpace(selectedPath);
|
||||
checkFolderWritePermission(selectedPath);
|
||||
}
|
||||
}, [visible, selectedPath]);
|
||||
}, [visible, checkFolderWritePermission, selectedPath]);
|
||||
|
||||
const downloaders = useMemo(() => {
|
||||
return getDownloadersForUris(repack?.uris ?? []);
|
||||
@ -84,12 +107,6 @@ export function DownloadSettingsModal({
|
||||
userPreferences?.realDebridApiToken,
|
||||
]);
|
||||
|
||||
const getDiskFreeSpace = (path: string) => {
|
||||
window.electron.getDiskFreeSpace(path).then((result) => {
|
||||
setDiskFreeSpace(result);
|
||||
});
|
||||
};
|
||||
|
||||
const handleChooseDownloadsPath = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
defaultPath: selectedPath,
|
||||
@ -124,7 +141,7 @@ export function DownloadSettingsModal({
|
||||
visible={visible}
|
||||
title={t("download_settings")}
|
||||
description={t("space_left_on_disk", {
|
||||
space: formatBytes(diskFreeSpace?.free ?? 0),
|
||||
space: formatBytes(diskFreeSpace ?? 0),
|
||||
})}
|
||||
onClose={onClose}
|
||||
>
|
||||
@ -168,14 +185,22 @@ export function DownloadSettingsModal({
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
}}
|
||||
>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
value={selectedPath}
|
||||
readOnly
|
||||
disabled
|
||||
label={t("download_path")}
|
||||
/>
|
||||
|
||||
error={
|
||||
hasWritePermission === false ? (
|
||||
<span
|
||||
className={styles.pathError}
|
||||
data-open-article="cannot-write-directory"
|
||||
>
|
||||
{t("no_write_permission")}
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
rightContent={
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
@ -184,7 +209,8 @@ export function DownloadSettingsModal({
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<p className={styles.hintText}>
|
||||
<Trans i18nKey="select_folder_hint" ns="game_details">
|
||||
@ -195,7 +221,11 @@ export function DownloadSettingsModal({
|
||||
|
||||
<Button
|
||||
onClick={handleStartClick}
|
||||
disabled={downloadStarting || selectedDownloader === null}
|
||||
disabled={
|
||||
downloadStarting ||
|
||||
selectedDownloader === null ||
|
||||
!hasWritePermission
|
||||
}
|
||||
>
|
||||
<DownloadIcon />
|
||||
{t("download_now")}
|
||||
|
@ -163,7 +163,7 @@ export function EditProfileModal(
|
||||
minLength={3}
|
||||
maxLength={50}
|
||||
containerProps={{ style: { width: "100%" } }}
|
||||
error={errors.displayName}
|
||||
error={errors.displayName?.message}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -105,7 +105,7 @@ export function ReportProfile() {
|
||||
{...register("description")}
|
||||
label={t("report_description")}
|
||||
placeholder={t("report_description_placeholder")}
|
||||
error={errors.description}
|
||||
error={errors.description?.message}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@ -150,7 +150,7 @@ export function AddDownloadSourceModal({
|
||||
{...register("url")}
|
||||
label={t("download_source_url")}
|
||||
placeholder={t("insert_valid_json_url")}
|
||||
error={errors.url}
|
||||
error={errors.url?.message}
|
||||
rightContent={
|
||||
<Button
|
||||
type="button"
|
||||
|
23
yarn.lock
23
yarn.lock
@ -4381,11 +4381,6 @@ chalk@^5.3.0:
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
|
||||
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
|
||||
|
||||
check-disk-space@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/check-disk-space/-/check-disk-space-3.4.0.tgz#eb8e69eee7a378fd12e35281b8123a8b4c4a8ff7"
|
||||
integrity sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==
|
||||
|
||||
chokidar@^3.5.3:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||
@ -4921,6 +4916,14 @@ dir-glob@^3.0.1:
|
||||
dependencies:
|
||||
path-type "^4.0.0"
|
||||
|
||||
diskusage@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/diskusage/-/diskusage-1.2.0.tgz#3e8ae42333d5d7e0c7d93e055d7fea9ea841bc88"
|
||||
integrity sha512-2u3OG3xuf5MFyzc4MctNRUKjjwK+UkovRYdD2ed/NZNZPrt0lqHnLKxGhlFVvAb4/oufIgQG3nWgwmeTbHOvXA==
|
||||
dependencies:
|
||||
es6-promise "^4.2.8"
|
||||
nan "^2.18.0"
|
||||
|
||||
dmg-builder@25.1.8:
|
||||
version "25.1.8"
|
||||
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-25.1.8.tgz#41f3b725edd896156e891016a44129e1bd580430"
|
||||
@ -5327,6 +5330,11 @@ es6-error@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
||||
integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==
|
||||
|
||||
es6-promise@^4.2.8:
|
||||
version "4.2.8"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
||||
integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
|
||||
|
||||
esbuild@^0.21.3, esbuild@^0.21.5:
|
||||
version "0.21.5"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
|
||||
@ -7425,6 +7433,11 @@ mz@^2.4.0:
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nan@^2.18.0:
|
||||
version "2.22.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.0.tgz#31bc433fc33213c97bad36404bb68063de604de3"
|
||||
integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==
|
||||
|
||||
nanoid@^3.3.7:
|
||||
version "3.3.7"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||
|
Loading…
Reference in New Issue
Block a user