diff --git a/backend/app/app.py b/backend/app/app.py index a838dc7a8..b6d54df7f 100644 --- a/backend/app/app.py +++ b/backend/app/app.py @@ -22,7 +22,7 @@ def get_categories() -> list[str]: @router.get("/category/{category}", tags=["app"]) def get_category( category: schemas.MainCategory, - filter_subcategories: list[str] = Query(None), + exclude_subcategories: list[str] = Query(None), page: int | None = None, per_page: int | None = None, locale: str = "en", @@ -35,7 +35,7 @@ def get_category( return response result = search.get_by_selected_categories( - [category], filter_subcategories, page, per_page, locale + [category], exclude_subcategories, page, per_page, locale ) return result @@ -45,7 +45,7 @@ def get_category( def get_subcategory( category: schemas.MainCategory, subcategory: str, - filter_subcategories: list[str] = Query(None), + exclude_subcategories: list[str] = Query(None), page: int | None = None, per_page: int | None = None, locale: str = "en", @@ -58,7 +58,7 @@ def get_subcategory( return response result = search.get_by_selected_category_and_subcategory( - category, subcategory, filter_subcategories, page, per_page, locale + category, subcategory, exclude_subcategories, page, per_page, locale ) return result diff --git a/backend/app/search.py b/backend/app/search.py index 50006802d..6e8d14ddb 100644 --- a/backend/app/search.py +++ b/backend/app/search.py @@ -96,7 +96,7 @@ def delete_apps(app_id_list): def get_by_selected_categories( selected_categories: list[schemas.MainCategory], - filter_subcategories: list[str], + exclude_subcategories: list[str], page: int | None, hits_per_page: int | None, locale: str, @@ -105,6 +105,12 @@ def get_by_selected_categories( f"categories = {category.value}" for category in selected_categories ] + exclude_subcategories_list = [ + f"sub_categories NOT IN [{exclude_subcategory}]" + for exclude_subcategory in exclude_subcategories + if exclude_subcategory is not None + ] + return _translate_name_and_summary( locale, client.index("apps").search( @@ -112,11 +118,7 @@ def get_by_selected_categories( { "filter": [ category_list, - ( - f"sub_categories NOT IN {filter_subcategories}" - if filter_subcategories is not None - else "" - ), + exclude_subcategories_list, "type IN [console-application, desktop-application]", "NOT icon IS NULL", ], @@ -131,11 +133,18 @@ def get_by_selected_categories( def get_by_selected_category_and_subcategory( selected_category: schemas.MainCategory, selected_subcategory: str, - filter_subcategories: list[str], + exclude_subcategories: list[str], page: int | None, hits_per_page: int | None, locale: str, ): + + exclude_subcategories_list = [ + f"sub_categories NOT IN [{exclude_subcategory}]" + for exclude_subcategory in exclude_subcategories + if exclude_subcategory is not None + ] + return _translate_name_and_summary( locale, client.index("apps").search( @@ -144,11 +153,7 @@ def get_by_selected_category_and_subcategory( "filter": [ f"main_categories = {selected_category.value}", f"sub_categories = {selected_subcategory}", - ( - f"sub_categories NOT IN {filter_subcategories}" - if filter_subcategories is not None - else "" - ), + exclude_subcategories_list, "type IN [console-application, desktop-application]", "NOT icon IS NULL", ], diff --git a/frontend/pages/apps/category/[category]/subcategories/[subcategory]/[page].tsx b/frontend/pages/apps/category/[category]/subcategories/[subcategory]/[page].tsx index 66829677d..4645cfbb4 100644 --- a/frontend/pages/apps/category/[category]/subcategories/[subcategory]/[page].tsx +++ b/frontend/pages/apps/category/[category]/subcategories/[subcategory]/[page].tsx @@ -8,7 +8,6 @@ import Breadcrumbs from "src/components/Breadcrumbs" import ApplicationCollection from "../../../../../../src/components/application/Collection" import { fetchSubcategory } from "../../../../../../src/fetchers" import { - Category, categoryToName, subcategoryToName, } from "../../../../../../src/types/Category" @@ -17,6 +16,7 @@ import { MeilisearchResponse, mapAppsIndexToAppstreamListItem, } from "src/meilisearch" +import { MainCategory } from "src/codegen" const ApplicationCategory = ({ applications, @@ -25,7 +25,7 @@ const ApplicationCategory = ({ }) => { const { t } = useTranslation() const router = useRouter() - const category = router.query.category as Category + const category = router.query.category as MainCategory let categoryName = categoryToName(category, t) const subcategory = router.query.subcategory as string let subcategoryName = subcategoryToName(category, subcategory, t) @@ -75,7 +75,7 @@ export const getStaticProps: GetStaticProps = async ({ locale, params }) => { } const applications = await fetchSubcategory( - params.category as keyof typeof Category, + params.category as keyof typeof MainCategory, params.subcategory as string, locale, params.page as unknown as number, diff --git a/frontend/pages/index-new.tsx b/frontend/pages/index-new.tsx new file mode 100644 index 000000000..7119f3479 --- /dev/null +++ b/frontend/pages/index-new.tsx @@ -0,0 +1,476 @@ +import { GetStaticProps } from "next" +import { serverSideTranslations } from "next-i18next/serverSideTranslations" + +import fetchCollection, { + fetchAppOfTheDay, + fetchAppsOfTheWeek, + fetchAppstream, + fetchCategory, + fetchSubcategory, +} from "../src/fetchers" +import { APPS_IN_PREVIEW_COUNT, IS_PRODUCTION } from "../src/env" +import { NextSeo } from "next-seo" +import { useTranslation } from "next-i18next" +import { + AppsIndex, + MeilisearchResponse, + mapAppsIndexToAppstreamListItem, +} from "src/meilisearch" +import { tryParseCategory } from "src/types/Category" +import ApplicationSection from "src/components/application/ApplicationSection" +import { HeroBanner } from "src/components/application/HeroBanner" +import { DesktopAppstream } from "src/types/Appstream" +import clsx from "clsx" +import { AppOfTheDay } from "src/components/application/AppOfTheDay" +import { formatISO, sub } from "date-fns" +import { useEffect, useState } from "react" +import MultiToggle from "src/components/MultiToggle" +import { useRouter } from "next/router" +import { Button } from "@/components/ui/button" +import Link from "next/link" +import { MainCategory } from "src/codegen" + +interface CategoryItem { + displayName: string + category: keyof typeof MainCategory + subcategory?: string + apps: MeilisearchResponse +} + +// const categoryOrder = [ +// "office", +// "graphics", +// "audiovideo", +// "education", +// "game", +// "network", +// "development", +// "science", +// "system", +// "utility", +// ] + +const homepageCategories = [ + { + displayName: "Office", + category: MainCategory.office, + subcategory_exclude: [], + }, + { + displayName: "Graphics", + category: MainCategory.graphics, + subcategory_exclude: [], + }, + { + displayName: "AudioVideo", + category: MainCategory.audiovideo, + subcategory_exclude: [], + }, + { + displayName: "Education", + category: MainCategory.education, + subcategory_exclude: [], + }, + { + displayName: "Emulator", + category: MainCategory.game, + subcategory: "Emulator", + subcategory_exclude: [], + }, + { + displayName: "Game Launcher", + category: MainCategory.game, + subcategory: "PackageManager", + subcategory_exclude: [], + }, + { + displayName: "Game", + category: MainCategory.game, + subcategory_exclude: ["Emulator", "PackageManager"], + }, + { + displayName: "Game Tools", + category: MainCategory.game, + subcategory_exclude: ["Emulator", "PackageManager"], + }, + { + displayName: "Network", + category: MainCategory.network, + subcategory_exclude: [], + }, + { + displayName: "Development", + category: MainCategory.development, + subcategory_exclude: [], + }, + { + displayName: "Science", + category: MainCategory.science, + subcategory_exclude: [], + }, + { + displayName: "System", + category: MainCategory.system, + subcategory_exclude: [], + }, + { + displayName: "Utility", + category: MainCategory.utility, + subcategory_exclude: [], + }, +] + +const CategorySection = ({ + topAppsByCategory, +}: { + topAppsByCategory: CategoryItem[] +}) => { + const { t } = useTranslation() + + return ( + <> + {topAppsByCategory.map((sectionData) => ( + + mapAppsIndexToAppstreamListItem(app), + )} + numberOfApps={6} + customHeader={ + <> +
+

+ {tryParseCategory(sectionData.displayName, t) ?? + t(sectionData.displayName)} +

+
+ + } + showMore={true} + moreText={t(`more-x`, { + category: + tryParseCategory(sectionData.displayName, t) ?? + t(sectionData.displayName), + })} + /> + ))} + + ) +} + +const TopSection = ({ + topApps, +}: { + topApps: { + name: string + apps: MeilisearchResponse + moreLink: string + }[] +}) => { + const { t } = useTranslation() + + const router = useRouter() + + const [selectedName, setSelectedName] = useState( + router?.query?.category?.toString() || topApps[0].name, + ) + + const [selectedApps, setSelectedApps] = useState<{ + name: string + apps: MeilisearchResponse + moreLink: string + }>(topApps.find((x) => x.name === selectedName) || topApps[0]) + + useEffect(() => { + if (router?.query?.category) { + setSelectedName(router.query.category.toString()) + } + }, [router?.query?.category]) + + useEffect(() => { + const foundApps = topApps.find( + (sectionData) => sectionData.name === selectedName, + ) + setSelectedApps(foundApps) + }, [selectedName, topApps]) + + return ( + + mapAppsIndexToAppstreamListItem(app), + )} + numberOfApps={APPS_IN_PREVIEW_COUNT} + customHeader={ + <> + ({ + id: x.name, + content: ( +
{t(x.name)}
+ ), + selected: x.name === selectedName, + onClick: () => { + const newQuery = { ...router.query } + newQuery.category = x.name + router.push({ query: newQuery }, undefined, { + scroll: false, + }) + }, + }))} + size={"lg"} + variant="secondary" + /> + + } + showMore={true} + moreText={t(`more-${selectedApps.name}`)} + /> + ) +} + +export default function Home({ + recentlyUpdated, + recentlyAdded, + trending, + popular, + topAppsByCategory, + heroBannerData, + appOfTheDayAppstream, + mobile, +}: { + recentlyUpdated: MeilisearchResponse + recentlyAdded: MeilisearchResponse + trending: MeilisearchResponse + popular: MeilisearchResponse + topAppsByCategory: CategoryItem[] + heroBannerData: { + app: { position: number; app_id: string; isFullscreen: boolean } + appstream: DesktopAppstream + }[] + appOfTheDayAppstream: DesktopAppstream + mobile: MeilisearchResponse +}) { + const { t } = useTranslation() + + return ( + <> + +
+
+ {heroBannerData.length > 0 && ( + + )} +
+ +
+
+
+
+ {t("flathub-the-linux-app-store")} +
+

+ {t("flathub-index-description")} +

+
+ + {!IS_PRODUCTION && ( + + )} +
+
+
+
+
+
+ + + + +
+ + ) +} + +export const getStaticProps: GetStaticProps = async ({ + locale, +}: { + locale: string +}) => { + const recentlyUpdated = await fetchCollection( + "recently-updated", + 1, + APPS_IN_PREVIEW_COUNT * 2, + locale, + ) + const popular = await fetchCollection( + "popular", + 1, + APPS_IN_PREVIEW_COUNT, + locale, + ) + const recentlyAdded = await fetchCollection( + "recently-added", + 1, + APPS_IN_PREVIEW_COUNT, + locale, + ) + const trending = await fetchCollection( + "trending", + 1, + APPS_IN_PREVIEW_COUNT, + locale, + ) + + const mobile = await fetchCollection( + "mobile", + 1, + APPS_IN_PREVIEW_COUNT, + locale, + ) + + let topAppsByCategory: CategoryItem[] = [] + + const categoryPromise = homepageCategories.map(async (category) => { + if (!category.subcategory) { + return { + displayName: category.displayName, + category: category.category, + apps: await fetchCategory( + category.category, + locale, + 1, + 6, + category.subcategory_exclude, + ), + } + } else { + return { + displayName: category.displayName, + category: category.category, + subcategory: category.subcategory, + apps: await fetchSubcategory( + category.category, + category.subcategory, + locale, + 1, + 6, + category.subcategory_exclude, + ), + } + } + }) + + topAppsByCategory = await Promise.all(categoryPromise) + + // remove duplicated apps + recentlyUpdated.hits = recentlyUpdated.hits + .filter( + (app) => !recentlyAdded.hits.some((addedApp) => addedApp.id === app.id), + ) + .slice(0, APPS_IN_PREVIEW_COUNT) + + const heroBannerApps = await fetchAppsOfTheWeek( + formatISO(new Date(), { representation: "date" }), + ) + const appOfTheDay = await fetchAppOfTheDay( + formatISO(new Date(), { representation: "date" }), + ) + + const heroBannerAppstreams = await Promise.all( + heroBannerApps.apps.map(async (app) => fetchAppstream(app.app_id, locale)), + ) + + const heroBannerData = heroBannerApps.apps.map((app) => { + return { + app: app, + appstream: heroBannerAppstreams.find((a) => a.id === app.app_id), + } + }) + + const appOfTheDayAppstream = await fetchAppstream(appOfTheDay.app_id, locale) + + return { + props: { + ...(await serverSideTranslations(locale, ["common"])), + recentlyUpdated, + recentlyAdded, + trending, + popular, + topAppsByCategory, + heroBannerData, + appOfTheDayAppstream, + mobile, + }, + revalidate: 900, + } +} diff --git a/frontend/pages/index.tsx b/frontend/pages/index.tsx index 255f5aef6..afee97429 100644 --- a/frontend/pages/index.tsx +++ b/frontend/pages/index.tsx @@ -15,7 +15,7 @@ import { MeilisearchResponse, mapAppsIndexToAppstreamListItem, } from "src/meilisearch" -import { Category, categoryToName } from "src/types/Category" +import { categoryToName } from "src/types/Category" import ApplicationSection from "src/components/application/ApplicationSection" import { HeroBanner } from "src/components/application/HeroBanner" import { DesktopAppstream } from "src/types/Appstream" @@ -27,25 +27,26 @@ import MultiToggle from "src/components/MultiToggle" import { useRouter } from "next/router" import { Button } from "@/components/ui/button" import Link from "next/link" +import { MainCategory } from "src/codegen" const categoryOrder = [ - Category.Office, - Category.Graphics, - Category.AudioVideo, - Category.Education, - Category.Game, - Category.Network, - Category.Development, - Category.Science, - Category.System, - Category.Utility, + MainCategory.office, + MainCategory.graphics, + MainCategory.audiovideo, + MainCategory.education, + MainCategory.game, + MainCategory.network, + MainCategory.development, + MainCategory.science, + MainCategory.system, + MainCategory.utility, ] const CategorySection = ({ topAppsByCategory, }: { topAppsByCategory: { - category: Category + category: MainCategory apps: MeilisearchResponse }[] }) => { @@ -167,7 +168,7 @@ export default function Home({ trending: MeilisearchResponse popular: MeilisearchResponse topAppsByCategory: { - category: Category + category: MainCategory apps: MeilisearchResponse }[] heroBannerData: { @@ -317,12 +318,12 @@ export const getStaticProps: GetStaticProps = async ({ ) let topAppsByCategory: { - category: Category + category: MainCategory apps: MeilisearchResponse }[] = [] - const categoryPromise = Object.keys(Category).map( - async (category: Category) => { + const categoryPromise = Object.keys(MainCategory).map( + async (category: MainCategory) => { return { category, apps: await fetchCategory(category, locale, 1, 6), diff --git a/frontend/pages/statistics.tsx b/frontend/pages/statistics.tsx index af5e0fca1..5247ccea7 100644 --- a/frontend/pages/statistics.tsx +++ b/frontend/pages/statistics.tsx @@ -9,11 +9,15 @@ import ListBox from "../src/components/application/ListBox" import { i18n, useTranslation } from "next-i18next" import { useTheme } from "next-themes" import { getIntlLocale } from "../src/localize" -import { Category, categoryToName, tryParseCategory } from "src/types/Category" +import { + categoryToName, + tryParseCategory, + tryParseSubCategory, +} from "src/types/Category" import { useRouter } from "next/router" import { useQuery } from "@tanstack/react-query" import { useUserContext } from "src/context/user-info" -import { Permission, StatsResult } from "src/codegen/model" +import { MainCategory, Permission, StatsResult } from "src/codegen/model" import { getQualityModerationStatsQualityModerationFailedByGuidelineGet, getRuntimeListRuntimesGet, @@ -307,13 +311,17 @@ const CategoryDistribution = ({ stats }: { stats: StatsResult }) => { categoryToName(x as Category, t)} + tickFormatter={(x) => + categoryToName(x.toLowerCase() as MainCategory, t) + } type="category" width={180} tickLine={false} /> categoryToName(x as Category, t)} + labelFormatter={(x) => + categoryToName(x.toLowerCase() as MainCategory, t) + } cursor={false} content={} /> @@ -327,7 +335,10 @@ const CategoryDistribution = ({ stats }: { stats: StatsResult }) => { } - labelFormatter={(x) => tryParseCategory(x, t)} + labelFormatter={(x) => + tryParseSubCategory(x, t) ?? + tryParseCategory(x.toLowerCase(), t) + } /> diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 89f68b397..96465b3cb 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -272,20 +272,7 @@ "open-user-menu": "Open User Menu", "show-more": "Show More", "show-less": "Show Less", - "more-trending": "More Trending", - "more-popular": "More Popular", - "more-new": "More New", - "more-updated": "More Updated", - "more-office": "More Productivity", - "more-graphics": "More Graphics & Photography", - "more-audiovideo": "More Audio & Video", - "more-education": "More Education", - "more-game": "More Games", - "more-network": "More Networking", - "more-development": "More Developer Tools", - "more-science": "More Science", - "more-system": "More System", - "more-utility": "More Utilities", + "more-x": "More {{category}}", "verified": "Verified", "app-is-verified": "This app is verified", "verification-instructions": "Verify your app ID to indicate that your Flathub upload is approved by the app developer. You'll need to prove that you have control of the app via a website on the app's domain or by checking access to the app's account on a source code hosting site. The verified badge will be shown alongside the app name, publisher name, and the way that the app ID was verified.", @@ -626,4 +613,4 @@ "hide-branding-preview": "Hide branding preview", "show-branding-preview": "Show branding preview" } -} +} \ No newline at end of file diff --git a/frontend/src/codegen/model/getCategoryCategoryCategoryGetParams.ts b/frontend/src/codegen/model/getCategoryCategoryCategoryGetParams.ts index 5132e77e6..66bd74bf5 100644 --- a/frontend/src/codegen/model/getCategoryCategoryCategoryGetParams.ts +++ b/frontend/src/codegen/model/getCategoryCategoryCategoryGetParams.ts @@ -6,7 +6,7 @@ */ export type GetCategoryCategoryCategoryGetParams = { - filter_subcategories?: string[] + exclude_subcategories?: string[] page?: number | null per_page?: number | null locale?: string diff --git a/frontend/src/codegen/model/getSubcategoryCategoryCategorySubcategoriesSubcategoryGetParams.ts b/frontend/src/codegen/model/getSubcategoryCategoryCategorySubcategoriesSubcategoryGetParams.ts index 23788a43e..8309519b8 100644 --- a/frontend/src/codegen/model/getSubcategoryCategoryCategorySubcategoriesSubcategoryGetParams.ts +++ b/frontend/src/codegen/model/getSubcategoryCategoryCategorySubcategoriesSubcategoryGetParams.ts @@ -6,7 +6,7 @@ */ export type GetSubcategoryCategoryCategorySubcategoriesSubcategoryGetParams = { - filter_subcategories?: string[] + exclude_subcategories?: string[] page?: number | null per_page?: number | null locale?: string diff --git a/frontend/src/env.ts b/frontend/src/env.ts index ccca7199b..12926b3de 100644 --- a/frontend/src/env.ts +++ b/frontend/src/env.ts @@ -1,4 +1,4 @@ -import { Category } from "./types/Category" +import { MainCategory } from "./codegen" const BASE_URI: string = process.env.NEXT_PUBLIC_API_BASE_URI @@ -140,10 +140,11 @@ export const MOBILE_APPS_URL = ( } export const CATEGORY_URL = ( - category: keyof typeof Category, + category: keyof typeof MainCategory, page?: number, per_page?: number, locale?: string, + exclude_subcategories?: string[], ): string => { const result = new URLSearchParams() @@ -158,15 +159,20 @@ export const CATEGORY_URL = ( if (locale) { result.append("locale", locale) } + + if (exclude_subcategories) { + result.append("exclude_subcategories", exclude_subcategories.join(",")) + } return `${BASE_URI}/category/${category}?${result.toString()}` } export const SUBCATEGORY_URL = ( - category: keyof typeof Category, + category: keyof typeof MainCategory, subcategory: string, page?: number, per_page?: number, locale?: string, + exclude_subcategories?: string[], ): string => { const result = new URLSearchParams() @@ -181,6 +187,10 @@ export const SUBCATEGORY_URL = ( if (locale) { result.append("locale", locale) } + + if (exclude_subcategories) { + result.append("exclude_subcategories", exclude_subcategories.join(",")) + } return `${BASE_URI}/category/${category}/subcategories/${subcategory}?${result.toString()}` } diff --git a/frontend/src/fetchers.ts b/frontend/src/fetchers.ts index 113873a5b..c33edea6b 100644 --- a/frontend/src/fetchers.ts +++ b/frontend/src/fetchers.ts @@ -1,5 +1,4 @@ import { Appstream } from "./types/Appstream" -import { Category } from "./types/Category" import { POPULAR_LAST_MONTH_URL, @@ -30,7 +29,12 @@ import { MeilisearchResponse, MeilisearchResponseLimited, } from "./meilisearch" -import { AppOfTheDay, AppsOfTheWeek, VendingConfig } from "./codegen" +import { + AppOfTheDay, + AppsOfTheWeek, + MainCategory, + VendingConfig, +} from "./codegen" import axios from "axios" export async function fetchAppstream( @@ -159,12 +163,15 @@ export default async function fetchCollection( } export async function fetchCategory( - category: keyof typeof Category, + category: keyof typeof MainCategory, locale: string, page?: number, per_page?: number, + exclude_subcategories?: string[], ): Promise> { - const appListRes = await fetch(CATEGORY_URL(category, page, per_page, locale)) + const appListRes = await fetch( + CATEGORY_URL(category, page, per_page, locale, exclude_subcategories), + ) const response: MeilisearchResponse = await appListRes.json() console.log( @@ -175,14 +182,22 @@ export async function fetchCategory( } export async function fetchSubcategory( - category: keyof typeof Category, + category: keyof typeof MainCategory, subcategory: string, locale: string, page?: number, per_page?: number, + exclude_subcategories?: string[], ): Promise> { const appListRes = await fetch( - SUBCATEGORY_URL(category, subcategory, page, per_page, locale), + SUBCATEGORY_URL( + category, + subcategory, + page, + per_page, + locale, + exclude_subcategories, + ), ) const response: MeilisearchResponse = await appListRes.json() diff --git a/frontend/src/types/Category.ts b/frontend/src/types/Category.ts index 4dd684fa8..ea5fd7eae 100644 --- a/frontend/src/types/Category.ts +++ b/frontend/src/types/Category.ts @@ -1,40 +1,28 @@ import { TFunction } from "i18next" +import { MainCategory } from "src/codegen" -export enum Category { - AudioVideo = "AudioVideo", - Development = "Development", - Education = "Education", - Game = "Game", - Graphics = "Graphics", - Network = "Network", - Office = "Office", - Science = "Science", - System = "System", - Utility = "Utility", -} - -export function stringToCategory(category: string): Category | undefined { +export function stringToCategory(category: string): MainCategory | undefined { switch (category.toLowerCase()) { case "audiovideo": - return Category.AudioVideo + return MainCategory.audiovideo case "development": - return Category.Development + return MainCategory.development case "education": - return Category.Education + return MainCategory.education case "game": - return Category.Game + return MainCategory.game case "graphics": - return Category.Graphics + return MainCategory.graphics case "network": - return Category.Network + return MainCategory.network case "office": - return Category.Office + return MainCategory.office case "science": - return Category.Science + return MainCategory.science case "system": - return Category.System + return MainCategory.system case "utility": - return Category.Utility + return MainCategory.utility default: return undefined } @@ -51,30 +39,45 @@ export function tryParseCategory( } } +export function tryParseSubCategory( + subcategory: string, + t: TFunction<"translation", undefined>, +): string | undefined { + try { + return gameCategoryToName(subcategory as GameCategory, t) + } catch { + try { + return audioVideoCategoryToName(subcategory as AudioVideoCategory, t) + } catch { + return undefined + } + } +} + export function categoryToName( - category: Category, + category: MainCategory, t: TFunction<"translation", undefined>, ): string { switch (category) { - case Category.AudioVideo: + case MainCategory.audiovideo: return t("audio-and-video") - case Category.Development: + case MainCategory.development: return t("developer-tools") - case Category.Game: + case MainCategory.game: return t("games") - case Category.Graphics: + case MainCategory.graphics: return t("graphics-and-photography") - case Category.Network: + case MainCategory.network: return t("networking") - case Category.Office: + case MainCategory.office: return t("productivity") - case Category.Utility: + case MainCategory.utility: return t("utilities") - case Category.Science: + case MainCategory.science: return t("science") - case Category.Education: + case MainCategory.education: return t("education") - case Category.System: + case MainCategory.system: return t("system") default: assertUnreachable(category) @@ -183,14 +186,14 @@ export function audioVideoCategoryToName( } export function subcategoryToName( - category: Category, + category: MainCategory, subcategory: string, t: TFunction<"translation", undefined>, ): string { - switch (category) { - case Category.Game: + switch (category.toLowerCase()) { + case MainCategory.game: return gameCategoryToName(subcategory as GameCategory, t) - case Category.AudioVideo: + case MainCategory.audiovideo: return audioVideoCategoryToName(subcategory as AudioVideoCategory, t) default: return subcategory @@ -201,11 +204,11 @@ function assertUnreachable(_x: never): never { throw new Error("Didn't expect to get here") } -export function getSubcategory(category: Category): string[] { - switch (category) { - case Category.Game: +export function getSubcategory(category: MainCategory): string[] { + switch (category.toLowerCase()) { + case MainCategory.game: return Object.keys(GameCategory) - case Category.AudioVideo: + case MainCategory.audiovideo: return Object.keys(AudioVideoCategory) } }