mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 22:06:17 +00:00
Compare commits
22 Commits
fix/LBX-29
...
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)",
|
||||
"game_has_no_executable": "Game has no executable selected",
|
||||
"sign_in": "Sign in",
|
||||
"friends": "Friends"
|
||||
"friends": "Friends",
|
||||
"aria_view_profile": "View profile",
|
||||
"resize_sidebar": "Resize sidebar"
|
||||
},
|
||||
"header": {
|
||||
"search": "Search games",
|
||||
@@ -35,7 +37,9 @@
|
||||
"search_results": "Search results",
|
||||
"settings": "Settings",
|
||||
"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": {
|
||||
"no_downloads_in_progress": "No downloads in progress",
|
||||
@@ -132,6 +136,7 @@
|
||||
"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.",
|
||||
"achievements": "Achievements",
|
||||
"achievement": "Achievement",
|
||||
"achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}",
|
||||
"cloud_save": "Cloud save",
|
||||
"cloud_save_description": "Save your progress in the cloud and continue playing on any device",
|
||||
@@ -358,11 +363,13 @@
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Achievement unlocked",
|
||||
"achievement_locked": "Achievement locked",
|
||||
"user_achievements": "{{displayName}}'s Achievements",
|
||||
"your_achievements": "Your Achievements",
|
||||
"unlocked_at": "Unlocked at:",
|
||||
"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": {
|
||||
"subscription_tour_title": "Hydra Cloud Subscription",
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
"queued": "{{title}} (Na fila)",
|
||||
"game_has_no_executable": "Jogo não possui executável selecionado",
|
||||
"sign_in": "Login",
|
||||
"friends": "Amigos"
|
||||
"friends": "Amigos",
|
||||
"aria_view_profile": "Ver perfil",
|
||||
"resize_sidebar": "Redimensionar barra lateral"
|
||||
},
|
||||
"header": {
|
||||
"search": "Buscar jogos",
|
||||
@@ -35,7 +37,9 @@
|
||||
"settings": "Ajustes",
|
||||
"home": "Início",
|
||||
"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": {
|
||||
"no_downloads_in_progress": "Sem downloads em andamento",
|
||||
@@ -128,6 +132,7 @@
|
||||
"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.",
|
||||
"achievements": "Conquistas",
|
||||
"achievement": "Conquista",
|
||||
"achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})",
|
||||
"cloud_save": "Salvamento em nuvem",
|
||||
"cloud_save_description": "Mantenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo",
|
||||
@@ -356,11 +361,13 @@
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Conquista desbloqueada",
|
||||
"achievement_locked": "Conquista bloqueada",
|
||||
"your_achievements": "Suas Conquistas",
|
||||
"user_achievements": "Conquistas de {{displayName}}",
|
||||
"unlocked_at": "Desbloqueado em:",
|
||||
"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": {
|
||||
"subscription_tour_title": "Assinatura Hydra Cloud",
|
||||
|
||||
@@ -65,6 +65,24 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||
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 (
|
||||
<>
|
||||
<header
|
||||
@@ -81,6 +99,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||
})}
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={location.key === "default"}
|
||||
title={t("back")}
|
||||
>
|
||||
<ArrowLeftIcon />
|
||||
</button>
|
||||
@@ -100,6 +119,8 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||
type="button"
|
||||
className={styles.actionButton}
|
||||
onClick={focusInput}
|
||||
tabIndex={-1}
|
||||
title={t("search")}
|
||||
>
|
||||
<SearchIcon />
|
||||
</button>
|
||||
@@ -121,6 +142,7 @@ export function Header({ onSearch, onClear, search }: HeaderProps) {
|
||||
type="button"
|
||||
onClick={onClear}
|
||||
className={styles.actionButton}
|
||||
title={t("clear_search")}
|
||||
>
|
||||
<XIcon />
|
||||
</button>
|
||||
|
||||
@@ -89,6 +89,7 @@ export function SidebarProfile() {
|
||||
type="button"
|
||||
className={styles.profileButton}
|
||||
onClick={handleProfileClick}
|
||||
aria-label={t("aria_view_profile")}
|
||||
>
|
||||
<div className={styles.profileButtonContent}>
|
||||
<Avatar
|
||||
|
||||
@@ -68,6 +68,26 @@ export function Sidebar() {
|
||||
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) => {
|
||||
setFilteredLibrary(
|
||||
sortedLibrary.filter((game) =>
|
||||
@@ -219,6 +239,7 @@ export function Sidebar() {
|
||||
type="button"
|
||||
className={styles.menuItemButton}
|
||||
onClick={(event) => handleSidebarGameClick(event, game)}
|
||||
aria-label={game.title}
|
||||
>
|
||||
{game.iconUrl ? (
|
||||
<img
|
||||
@@ -245,6 +266,8 @@ export function Sidebar() {
|
||||
type="button"
|
||||
className={styles.handle}
|
||||
onMouseDown={handleMouseDown}
|
||||
onKeyDown={handleKeyDown}
|
||||
title={t("resize_sidebar")}
|
||||
/>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { average } from "color.js";
|
||||
import Color from "color";
|
||||
import { Link } from "@renderer/components";
|
||||
import { ComparedAchievementList } from "./compared-achievement-list";
|
||||
import { TFunction } from "i18next/typescript/t";
|
||||
|
||||
interface UserInfo {
|
||||
id: string;
|
||||
@@ -39,10 +40,35 @@ interface AchievementSummaryProps {
|
||||
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) {
|
||||
const { t } = useTranslation("achievement");
|
||||
const { userDetails, hasActiveSubscription } = useUserDetails();
|
||||
const { handleClickOpenCheckout } = useContext(gameDetailsContext);
|
||||
const { handleClickOpenCheckout, gameTitle } = useContext(gameDetailsContext);
|
||||
|
||||
const getProfileImage = (
|
||||
user: Pick<UserInfo, "profileImageUrl" | "displayName">
|
||||
@@ -124,6 +150,8 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
||||
alignItems: "center",
|
||||
padding: `${SPACING_UNIT}px`,
|
||||
}}
|
||||
role="region"
|
||||
aria-label={ariaLabelSummary(t, gameTitle, user)}
|
||||
>
|
||||
{getProfileImage(user)}
|
||||
<div
|
||||
@@ -178,7 +206,12 @@ function AchievementList({ achievements }: AchievementListProps) {
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{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
|
||||
className={styles.listItemImage({
|
||||
unlocked: achievement.unlocked,
|
||||
|
||||
@@ -140,7 +140,7 @@ export function GallerySlider() {
|
||||
direction: "left",
|
||||
})}
|
||||
aria-label={t("previous_screenshot")}
|
||||
tabIndex={0}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ChevronLeftIcon size={36} />
|
||||
</button>
|
||||
@@ -153,7 +153,7 @@ export function GallerySlider() {
|
||||
direction: "right",
|
||||
})}
|
||||
aria-label={t("next_screenshot")}
|
||||
tabIndex={0}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ChevronRightIcon size={36} />
|
||||
</button>
|
||||
@@ -169,6 +169,7 @@ export function GallerySlider() {
|
||||
})}
|
||||
onClick={() => setMediaIndex(i)}
|
||||
aria-label={t("open_screenshot", { number: i + 1 })}
|
||||
onFocus={() => setMediaIndex(i)}
|
||||
>
|
||||
<img
|
||||
src={media.thumbnail}
|
||||
|
||||
@@ -38,6 +38,9 @@ export function HowLongToBeatSection({
|
||||
<li
|
||||
key={category.title}
|
||||
className={styles.howLongToBeatCategory}
|
||||
aria-label={`${category.title}, ${getDuration(
|
||||
category.duration
|
||||
)}`}
|
||||
>
|
||||
<p
|
||||
className={styles.howLongToBeatCategoryLabel}
|
||||
|
||||
@@ -194,6 +194,10 @@ export function Sidebar() {
|
||||
})}
|
||||
className={styles.listItem}
|
||||
title={achievement.description}
|
||||
aria-label={`
|
||||
${t("achievement")} ${index + 1},
|
||||
${achievement.displayName}
|
||||
`}
|
||||
>
|
||||
<img
|
||||
className={styles.listItemImage({
|
||||
|
||||
Reference in New Issue
Block a user