Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions devtools/decky-mock/e2e/deckyhub.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,22 @@ test("Discover filters by category", async ({ page }) => {
await expect(app.getByRole("heading", { name: "MAKO Decky" })).toBeVisible();
});

test("Discover sorts A to Z, Z to A and by most recently added", async ({ page }) => {
const app = mock(page);
await app.getByRole("button", { name: "/deckyhub/discover" }).click();
// MAKO Decky is the registry's first entry, Unifideck its third.
const order = async () => {
const names = await app.getByRole("heading").allTextContents();
return names.indexOf("MAKO Decky") - names.indexOf("Unifideck");
};

await expect.poll(order).toBeLessThan(0);
await app.getByLabel("Sort").selectOption("added");
await expect.poll(order).toBeGreaterThan(0);
await app.getByLabel("Sort").selectOption("za");
await expect.poll(order).toBeGreaterThan(0);
});

test("A GitHub rate limit surfaces a friendly message instead of a raw status code", async ({ page }) => {
const resetInTwoMinutes = Math.floor(Date.now() / 1000) + 120;
await page.route("https://api.github.com/repos/eugeniosegala/MAKO/releases?per_page=20", (route) =>
Expand Down
4 changes: 3 additions & 1 deletion devtools/decky-mock/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ export function TextField({
onChange,
bIsPassword,
bShowClearAction,
"aria-label": ariaLabel,
}: {
"aria-label"?: string;
label?: string;
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
Expand All @@ -150,7 +152,7 @@ export function TextField({
return (
<label className="steam-field-label">
{label}
<input type={bIsPassword ? "password" : "text"} value={value} onChange={onChange} className="steam-input" />
<input type={bIsPassword ? "password" : "text"} value={value} onChange={onChange} aria-label={ariaLabel} className="steam-input" />
{bShowClearAction && value && (
<button
type="button"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "deckyhub",
"version": "2.1.1-rc.29",
"version": "2.1.1-rc.30",
"description": "Discover, track, and safely download GitHub releases for Steam Deck tools from Gaming Mode.",
"author": "Andrii Shevchuk-Yuhanets",
"private": true,
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const de: Record<MessageKey, string> = {
"filter.all": "Alle",
"filter.installed": "Installiert",
"filter.notInstalled": "Nicht installiert",
"filter.sort": "Sortieren",
"sort.az": "A bis Z",
"sort.za": "Z bis A",
"sort.added": "Zuletzt hinzugefügt",

"content.loading": "Wird geladen…",
"content.loadErrorTitle": "DeckyHub konnte nicht geladen werden",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const en = {
"filter.all": "All",
"filter.installed": "Installed",
"filter.notInstalled": "Not Installed",
"filter.sort": "Sort",
"sort.az": "A to Z",
"sort.za": "Z to A",
"sort.added": "Recently added",

"content.loading": "Loading…",
"content.loadErrorTitle": "Could Not Load DeckyHub",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const es: Record<MessageKey, string> = {
"filter.all": "Todos",
"filter.installed": "Instalado",
"filter.notInstalled": "No instalado",
"filter.sort": "Ordenar",
"sort.az": "A a Z",
"sort.za": "Z a A",
"sort.added": "Añadidos recientemente",

"content.loading": "Cargando…",
"content.loadErrorTitle": "No se pudo cargar DeckyHub",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const fr: Record<MessageKey, string> = {
"filter.all": "Tous",
"filter.installed": "Installé",
"filter.notInstalled": "Non installé",
"filter.sort": "Trier",
"sort.az": "A à Z",
"sort.za": "Z à A",
"sort.added": "Ajoutés récemment",

"content.loading": "Chargement…",
"content.loadErrorTitle": "Impossible de charger DeckyHub",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const ja: Record<MessageKey, string> = {
"filter.all": "すべて",
"filter.installed": "インストール済み",
"filter.notInstalled": "未インストール",
"filter.sort": "並べ替え",
"sort.az": "A→Z",
"sort.za": "Z→A",
"sort.added": "最近追加",

"content.loading": "読み込み中…",
"content.loadErrorTitle": "DeckyHubを読み込めませんでした",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const ru: Record<MessageKey, string> = {
"filter.all": "Все",
"filter.installed": "Установлено",
"filter.notInstalled": "Не установлено",
"filter.sort": "Сортировка",
"sort.az": "А–Я",
"sort.za": "Я–А",
"sort.added": "Недавно добавленные",

"content.loading": "Загрузка…",
"content.loadErrorTitle": "Не удалось загрузить DeckyHub",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/uk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const uk: Record<MessageKey, string> = {
"filter.all": "Усі",
"filter.installed": "Встановлено",
"filter.notInstalled": "Не встановлено",
"filter.sort": "Сортування",
"sort.az": "А–Я",
"sort.za": "Я–А",
"sort.added": "Нещодавно додані",

"content.loading": "Завантаження…",
"content.loadErrorTitle": "Не вдалося завантажити DeckyHub",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const zh: Record<MessageKey, string> = {
"filter.all": "全部",
"filter.installed": "已安装",
"filter.notInstalled": "未安装",
"filter.sort": "排序",
"sort.az": "A 到 Z",
"sort.za": "Z 到 A",
"sort.added": "最近添加",

"content.loading": "加载中…",
"content.loadErrorTitle": "无法加载 DeckyHub",
Expand Down
42 changes: 37 additions & 5 deletions src/pages/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function Content({ fullPage }: { fullPage?: View }) {
const [installedFilter, setInstalledFilter] = useState("All");
// "" means every category.
const [categoryFilter, setCategoryFilter] = useState("");
const [sortOrder, setSortOrder] = useState<"az" | "za" | "added">("az");
const [columnsPerRow, setColumnsPerRow] = useState(DEFAULT_COLUMNS_PER_ROW);
const [repoPreferences, setRepoPreferences] = useState<Record<string, RepoPreference>>({});
const [hiddenButtons, setHiddenButtons] = useState<HideableButton[]>([]);
Expand Down Expand Up @@ -166,9 +167,18 @@ export function Content({ fullPage }: { fullPage?: View }) {
<PanelSectionRow>
<Focusable flow-children="right" style={{ display: "flex", gap: 12 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<TextField label={t("filter.search")} value={query} bShowClearAction onChange={(event) => setQuery(event.currentTarget.value)} />
{/* Own label instead of TextField's, which Steam renders uppercase and padded, out of line with the dropdowns. */}
<div style={{ fontSize: "0.85em" }}>
<div style={{ marginBottom: 4 }}>{t("filter.search")}</div>
<TextField
{...({ "aria-label": t("filter.search") } as any)}
value={query}
bShowClearAction
onChange={(event) => setQuery(event.currentTarget.value)}
/>
</div>
</div>
<div style={{ flex: "0 0 220px" }}>
<div style={{ flex: "0 0 180px" }}>
<div style={{ fontSize: "0.85em" }}>
<div style={{ marginBottom: 4 }}>{t("filter.status")}</div>
<Dropdown
Expand All @@ -184,7 +194,7 @@ export function Content({ fullPage }: { fullPage?: View }) {
/>
</div>
</div>
<div style={{ flex: "0 0 220px" }}>
<div style={{ flex: "0 0 180px" }}>
<div style={{ fontSize: "0.85em" }}>
<div style={{ marginBottom: 4 }}>{t("filter.category")}</div>
<Dropdown
Expand All @@ -196,6 +206,22 @@ export function Content({ fullPage }: { fullPage?: View }) {
/>
</div>
</div>
<div style={{ flex: "0 0 180px" }}>
<div style={{ fontSize: "0.85em" }}>
<div style={{ marginBottom: 4 }}>{t("filter.sort")}</div>
<Dropdown
{...({ "aria-label": t("filter.sort"), style: { width: "100%" } } as any)}
menuLabel={t("filter.sort")}
rgOptions={[
{ label: t("sort.az"), data: "az" },
{ label: t("sort.za"), data: "za" },
{ label: t("sort.added"), data: "added" },
]}
selectedOption={sortOrder}
onChange={({ data }) => setSortOrder(data)}
/>
</div>
</div>
</Focusable>
</PanelSectionRow>
</PanelSection>
Expand All @@ -210,8 +236,14 @@ export function Content({ fullPage }: { fullPage?: View }) {
(installedFilter === "All" || (installedFilter === "Installed") === Boolean(app.installedVersion)) &&
(!activeCategory || app.category === activeCategory),
)
// Apps with an update first; sort is stable, so registry order holds otherwise.
.sort((a, b) => Number(b.updateAvailable === true) - Number(a.updateAvailable === true));
// Apps with an update first; Updates keeps registry order otherwise, Discover follows the Sort
// dropdown. The registry only ever appends (custom repos come after it), so "Recently added" is
// that order reversed.
.sort((a, b) =>
Number(b.updateAvailable === true) - Number(a.updateAvailable === true) || (view === "updates" ? 0
: sortOrder === "added" ? apps.indexOf(b) - apps.indexOf(a)
: sortOrder === "za" ? b.name.localeCompare(a.name)
: a.name.localeCompare(b.name)));
const updateCount = apps.filter((app) => app.updateAvailable).length;
// Decky plugins the Updates page can update in one batch (see updateDeckyPlugins).
const batchUpdates = view === "updates" && !hiddenButtons.includes("update")
Expand Down
Loading