mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
Compare commits
22 Commits
9769eecec6
...
github/for
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba9232b821 | ||
|
|
a432306b1d | ||
|
|
69d96cc290 | ||
|
|
8f4919615f | ||
|
|
a25a960235 | ||
|
|
c754710171 | ||
|
|
bf6ce2b465 | ||
|
|
3adc8662dc | ||
|
|
2a6346cb69 | ||
|
|
455016c1a7 | ||
|
|
e0ec79b105 | ||
|
|
ba7e4c979d | ||
|
|
a54983c339 | ||
|
|
b754b1e052 | ||
|
|
79763b6072 | ||
|
|
3ff15d2d61 | ||
|
|
50303251a2 | ||
|
|
012f872f60 | ||
|
|
e9f68977fe | ||
|
|
c5d4db0a1e | ||
|
|
3b02a3c43f | ||
|
|
ab7625a314 |
@@ -25,7 +25,9 @@
|
|||||||
"queued": "{{title}} (Queued)",
|
"queued": "{{title}} (Queued)",
|
||||||
"game_has_no_executable": "Game has no executable selected",
|
"game_has_no_executable": "Game has no executable selected",
|
||||||
"sign_in": "Sign in",
|
"sign_in": "Sign in",
|
||||||
"friends": "Friends"
|
"friends": "Friends",
|
||||||
|
"aria_view_profile": "View profile",
|
||||||
|
"resize_sidebar": "Resize sidebar"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Search games",
|
"search": "Search games",
|
||||||
@@ -35,7 +37,9 @@
|
|||||||
"search_results": "Search results",
|
"search_results": "Search results",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"version_available_install": "Version {{version}} available. Click here to restart and install.",
|
"version_available_install": "Version {{version}} available. Click here to restart and install.",
|
||||||
"version_available_download": "Version {{version}} available. Click here to download."
|
"version_available_download": "Version {{version}} available. Click here to download.",
|
||||||
|
"back": "Back",
|
||||||
|
"clear_search": "Clear search"
|
||||||
},
|
},
|
||||||
"bottom_panel": {
|
"bottom_panel": {
|
||||||
"no_downloads_in_progress": "No downloads in progress",
|
"no_downloads_in_progress": "No downloads in progress",
|
||||||
@@ -132,6 +136,7 @@
|
|||||||
"warning": "Warning:",
|
"warning": "Warning:",
|
||||||
"hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util it's completed. If Hydra closes before completing, you will lose your progress.",
|
"hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util it's completed. If Hydra closes before completing, you will lose your progress.",
|
||||||
"achievements": "Achievements",
|
"achievements": "Achievements",
|
||||||
|
"achievement": "Achievement",
|
||||||
"achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}",
|
"achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}",
|
||||||
"cloud_save": "Cloud save",
|
"cloud_save": "Cloud save",
|
||||||
"cloud_save_description": "Save your progress in the cloud and continue playing on any device",
|
"cloud_save_description": "Save your progress in the cloud and continue playing on any device",
|
||||||
@@ -358,11 +363,13 @@
|
|||||||
},
|
},
|
||||||
"achievement": {
|
"achievement": {
|
||||||
"achievement_unlocked": "Achievement unlocked",
|
"achievement_unlocked": "Achievement unlocked",
|
||||||
|
"achievement_locked": "Achievement locked",
|
||||||
"user_achievements": "{{displayName}}'s Achievements",
|
"user_achievements": "{{displayName}}'s Achievements",
|
||||||
"your_achievements": "Your Achievements",
|
"your_achievements": "Your Achievements",
|
||||||
"unlocked_at": "Unlocked at:",
|
"unlocked_at": "Unlocked at:",
|
||||||
"subscription_needed": "A Hydra Cloud subscription is required to see this content",
|
"subscription_needed": "A Hydra Cloud subscription is required to see this content",
|
||||||
"new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games"
|
"new_achievements_unlocked": "Unlocked {{achievementCount}} new achievements from {{gameCount}} games",
|
||||||
|
"aria_achievement_summary": "{{userDisplayName}} achievements for {{gameTitle}}, {{userAchievementCount}} unlocked of {{userTotalAchievementCount}}, {{percentage}} completed"
|
||||||
},
|
},
|
||||||
"tour": {
|
"tour": {
|
||||||
"subscription_tour_title": "Hydra Cloud Subscription",
|
"subscription_tour_title": "Hydra Cloud Subscription",
|
||||||
|
|||||||
@@ -25,7 +25,9 @@
|
|||||||
"queued": "{{title}} (Na fila)",
|
"queued": "{{title}} (Na fila)",
|
||||||
"game_has_no_executable": "Jogo não possui executável selecionado",
|
"game_has_no_executable": "Jogo não possui executável selecionado",
|
||||||
"sign_in": "Login",
|
"sign_in": "Login",
|
||||||
"friends": "Amigos"
|
"friends": "Amigos",
|
||||||
|
"aria_view_profile": "Ver perfil",
|
||||||
|
"resize_sidebar": "Redimensionar barra lateral"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Buscar jogos",
|
"search": "Buscar jogos",
|
||||||
@@ -35,7 +37,9 @@
|
|||||||
"settings": "Ajustes",
|
"settings": "Ajustes",
|
||||||
"home": "Início",
|
"home": "Início",
|
||||||
"version_available_install": "Versão {{version}} disponível. Clique aqui para reiniciar e instalar.",
|
"version_available_install": "Versão {{version}} disponível. Clique aqui para reiniciar e instalar.",
|
||||||
"version_available_download": "Versão {{version}} disponível. Clique aqui para fazer o download."
|
"version_available_download": "Versão {{version}} disponível. Clique aqui para fazer o download.",
|
||||||
|
"back": "Voltar",
|
||||||
|
"clear_search": "Limpar busca"
|
||||||
},
|
},
|
||||||
"bottom_panel": {
|
"bottom_panel": {
|
||||||
"no_downloads_in_progress": "Sem downloads em andamento",
|
"no_downloads_in_progress": "Sem downloads em andamento",
|
||||||
@@ -128,6 +132,7 @@
|
|||||||
"warning": "Aviso:",
|
"warning": "Aviso:",
|
||||||
"hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.",
|
"hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.",
|
||||||
"achievements": "Conquistas",
|
"achievements": "Conquistas",
|
||||||
|
"achievement": "Conquista",
|
||||||
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
||||||
"cloud_save": "Salvamento em nuvem",
|
"cloud_save": "Salvamento em nuvem",
|
||||||
"cloud_save_description": "Mantenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo",
|
"cloud_save_description": "Mantenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo",
|
||||||
@@ -356,11 +361,13 @@
|
|||||||
},
|
},
|
||||||
"achievement": {
|
"achievement": {
|
||||||
"achievement_unlocked": "Conquista desbloqueada",
|
"achievement_unlocked": "Conquista desbloqueada",
|
||||||
|
"achievement_locked": "Conquista bloqueada",
|
||||||
"your_achievements": "Suas Conquistas",
|
"your_achievements": "Suas Conquistas",
|
||||||
"user_achievements": "Conquistas de {{displayName}}",
|
"user_achievements": "Conquistas de {{displayName}}",
|
||||||
"unlocked_at": "Desbloqueado em:",
|
"unlocked_at": "Desbloqueado em:",
|
||||||
"subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo",
|
"subscription_needed": "Você precisa de uma assinatura Hydra Cloud para visualizar este conteúdo",
|
||||||
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos"
|
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos",
|
||||||
|
"aria_achievement_summary": "Conquistas de {{userDisplayName}} em {{gameTitle}}, {{userAchievementCount}} desbloqueadas de {{userTotalAchievementCount}}, {{percentage}} concluídas"
|
||||||
},
|
},
|
||||||
"tour": {
|
"tour": {
|
||||||
"subscription_tour_title": "Assinatura Hydra Cloud",
|
"subscription_tour_title": "Assinatura Hydra Cloud",
|
||||||
|
|||||||
@@ -65,6 +65,24 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||||||
navigate(-1);
|
navigate(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.onkeydown = (event: KeyboardEvent) => {
|
||||||
|
const { key, ctrlKey } = event;
|
||||||
|
if (!isFocused && ctrlKey && key === "k") {
|
||||||
|
focusInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFocused && key === "Escape" && inputRef.current) {
|
||||||
|
inputRef.current.blur();
|
||||||
|
handleBlur();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.onkeydown = null;
|
||||||
|
};
|
||||||
|
}, [isFocused]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header
|
<header
|
||||||
@@ -81,6 +99,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||||||
})}
|
})}
|
||||||
onClick={handleBackButtonClick}
|
onClick={handleBackButtonClick}
|
||||||
disabled={location.key === "default"}
|
disabled={location.key === "default"}
|
||||||
|
title={t("back")}
|
||||||
>
|
>
|
||||||
<ArrowLeftIcon />
|
<ArrowLeftIcon />
|
||||||
</button>
|
</button>
|
||||||
@@ -100,6 +119,8 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
className={styles.actionButton}
|
className={styles.actionButton}
|
||||||
onClick={focusInput}
|
onClick={focusInput}
|
||||||
|
tabIndex={-1}
|
||||||
|
title={t("search")}
|
||||||
>
|
>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</button>
|
</button>
|
||||||
@@ -121,6 +142,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClear}
|
onClick={onClear}
|
||||||
className={styles.actionButton}
|
className={styles.actionButton}
|
||||||
|
title={t("clear_search")}
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ export function SidebarProfile() {
|
|||||||
type="button"
|
type="button"
|
||||||
className={styles.profileButton}
|
className={styles.profileButton}
|
||||||
onClick={handleProfileClick}
|
onClick={handleProfileClick}
|
||||||
|
aria-label={t("aria_view_profile")}
|
||||||
>
|
>
|
||||||
<div className={styles.profileButtonContent}>
|
<div className={styles.profileButtonContent}>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|||||||
@@ -68,6 +68,26 @@ export function Sidebar() {
|
|||||||
sidebarRef.current?.clientWidth || SIDEBAR_INITIAL_WIDTH;
|
sidebarRef.current?.clientWidth || SIDEBAR_INITIAL_WIDTH;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (
|
||||||
|
event
|
||||||
|
) => {
|
||||||
|
const { key } = event;
|
||||||
|
|
||||||
|
if (key === "ArrowRight") {
|
||||||
|
setSidebarWidth((prevWidth) =>
|
||||||
|
prevWidth < SIDEBAR_INITIAL_WIDTH
|
||||||
|
? SIDEBAR_INITIAL_WIDTH
|
||||||
|
: SIDEBAR_MAX_WIDTH
|
||||||
|
);
|
||||||
|
} else if (key === "ArrowLeft") {
|
||||||
|
setSidebarWidth((prevWidth) =>
|
||||||
|
prevWidth > SIDEBAR_INITIAL_WIDTH
|
||||||
|
? SIDEBAR_INITIAL_WIDTH
|
||||||
|
: SIDEBAR_MIN_WIDTH
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||||
setFilteredLibrary(
|
setFilteredLibrary(
|
||||||
sortedLibrary.filter((game) =>
|
sortedLibrary.filter((game) =>
|
||||||
@@ -219,6 +239,7 @@ export function Sidebar() {
|
|||||||
type="button"
|
type="button"
|
||||||
className={styles.menuItemButton}
|
className={styles.menuItemButton}
|
||||||
onClick={(event) => handleSidebarGameClick(event, game)}
|
onClick={(event) => handleSidebarGameClick(event, game)}
|
||||||
|
aria-label={game.title}
|
||||||
>
|
>
|
||||||
{game.iconUrl ? (
|
{game.iconUrl ? (
|
||||||
<img
|
<img
|
||||||
@@ -245,6 +266,8 @@ export function Sidebar() {
|
|||||||
type="button"
|
type="button"
|
||||||
className={styles.handle}
|
className={styles.handle}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
title={t("resize_sidebar")}
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { average } from "color.js";
|
|||||||
import Color from "color";
|
import Color from "color";
|
||||||
import { Link } from "@renderer/components";
|
import { Link } from "@renderer/components";
|
||||||
import { ComparedAchievementList } from "./compared-achievement-list";
|
import { ComparedAchievementList } from "./compared-achievement-list";
|
||||||
|
import { TFunction } from "i18next/typescript/t";
|
||||||
|
|
||||||
interface UserInfo {
|
interface UserInfo {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -39,10 +40,35 @@ interface AchievementSummaryProps {
|
|||||||
isComparison?: boolean;
|
isComparison?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ariaLabelSummary = (
|
||||||
|
t: TFunction,
|
||||||
|
gameTitle: string,
|
||||||
|
user: UserInfo
|
||||||
|
): string => {
|
||||||
|
return t("aria_achievement_summary", {
|
||||||
|
userDisplayName: user.displayName,
|
||||||
|
gameTitle: gameTitle,
|
||||||
|
userAchievementCount: user.unlockedAchievementCount,
|
||||||
|
userTotalAchievementCount: user.totalAchievementCount,
|
||||||
|
percentage: formatDownloadProgress(
|
||||||
|
user.unlockedAchievementCount / user.totalAchievementCount
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ariaLabelAchievement = (
|
||||||
|
t: TFunction,
|
||||||
|
achievement: UserAchievement
|
||||||
|
): string => {
|
||||||
|
return `${
|
||||||
|
achievement.unlocked ? t("achievement_unlocked") : t("achievement_locked")
|
||||||
|
}, ${achievement.displayName}, ${achievement.description}`;
|
||||||
|
};
|
||||||
|
|
||||||
function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
||||||
const { t } = useTranslation("achievement");
|
const { t } = useTranslation("achievement");
|
||||||
const { userDetails, hasActiveSubscription } = useUserDetails();
|
const { userDetails, hasActiveSubscription } = useUserDetails();
|
||||||
const { handleClickOpenCheckout } = useContext(gameDetailsContext);
|
const { handleClickOpenCheckout, gameTitle } = useContext(gameDetailsContext);
|
||||||
|
|
||||||
const getProfileImage = (
|
const getProfileImage = (
|
||||||
user: Pick<UserInfo, "profileImageUrl" | "displayName">
|
user: Pick<UserInfo, "profileImageUrl" | "displayName">
|
||||||
@@ -124,6 +150,8 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: `${SPACING_UNIT}px`,
|
padding: `${SPACING_UNIT}px`,
|
||||||
}}
|
}}
|
||||||
|
role="region"
|
||||||
|
aria-label={ariaLabelSummary(t, gameTitle, user)}
|
||||||
>
|
>
|
||||||
{getProfileImage(user)}
|
{getProfileImage(user)}
|
||||||
<div
|
<div
|
||||||
@@ -178,7 +206,12 @@ function AchievementList({ achievements }: AchievementListProps) {
|
|||||||
return (
|
return (
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{achievements.map((achievement, index) => (
|
{achievements.map((achievement, index) => (
|
||||||
<li key={index} className={styles.listItem} style={{ display: "flex" }}>
|
<li
|
||||||
|
key={index}
|
||||||
|
className={styles.listItem}
|
||||||
|
style={{ display: "flex" }}
|
||||||
|
aria-label={ariaLabelAchievement(t, achievement)}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
className={styles.listItemImage({
|
className={styles.listItemImage({
|
||||||
unlocked: achievement.unlocked,
|
unlocked: achievement.unlocked,
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export function GallerySlider() {
|
|||||||
direction: "left",
|
direction: "left",
|
||||||
})}
|
})}
|
||||||
aria-label={t("previous_screenshot")}
|
aria-label={t("previous_screenshot")}
|
||||||
tabIndex={0}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<ChevronLeftIcon size={36} />
|
<ChevronLeftIcon size={36} />
|
||||||
</button>
|
</button>
|
||||||
@@ -153,7 +153,7 @@ export function GallerySlider() {
|
|||||||
direction: "right",
|
direction: "right",
|
||||||
})}
|
})}
|
||||||
aria-label={t("next_screenshot")}
|
aria-label={t("next_screenshot")}
|
||||||
tabIndex={0}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<ChevronRightIcon size={36} />
|
<ChevronRightIcon size={36} />
|
||||||
</button>
|
</button>
|
||||||
@@ -169,6 +169,7 @@ export function GallerySlider() {
|
|||||||
})}
|
})}
|
||||||
onClick={() => setMediaIndex(i)}
|
onClick={() => setMediaIndex(i)}
|
||||||
aria-label={t("open_screenshot", { number: i + 1 })}
|
aria-label={t("open_screenshot", { number: i + 1 })}
|
||||||
|
onFocus={() => setMediaIndex(i)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={media.thumbnail}
|
src={media.thumbnail}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export function HowLongToBeatSection({
|
|||||||
<li
|
<li
|
||||||
key={category.title}
|
key={category.title}
|
||||||
className={styles.howLongToBeatCategory}
|
className={styles.howLongToBeatCategory}
|
||||||
|
aria-label={`${category.title}, ${getDuration(
|
||||||
|
category.duration
|
||||||
|
)}`}
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
className={styles.howLongToBeatCategoryLabel}
|
className={styles.howLongToBeatCategoryLabel}
|
||||||
|
|||||||
@@ -194,6 +194,10 @@ export function Sidebar() {
|
|||||||
})}
|
})}
|
||||||
className={styles.listItem}
|
className={styles.listItem}
|
||||||
title={achievement.description}
|
title={achievement.description}
|
||||||
|
aria-label={`
|
||||||
|
${t("achievement")} ${index + 1},
|
||||||
|
${achievement.displayName}
|
||||||
|
`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className={styles.listItemImage({
|
className={styles.listItemImage({
|
||||||
|
|||||||
Reference in New Issue
Block a user