Add language filter for a lot of pages
This commit is contained in:
parent
3c7b9aa2d6
commit
872f31a6a3
|
@ -2,10 +2,7 @@ import { Chip } from "components/Chip";
|
|||
import { Markdawn } from "components/Markdown/Markdawn";
|
||||
import { RecorderChip } from "components/RecorderChip";
|
||||
import { ToolTip } from "components/ToolTip";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { filterHasAttributes, isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { ContentStatus, useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
|
@ -40,8 +37,7 @@ export const Credits = ({
|
|||
authors = [],
|
||||
notes,
|
||||
}: Props): JSX.Element => {
|
||||
const { format, formatStatusDescription, formatStatusLabel } = useFormat();
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
const { format, formatStatusDescription, formatStatusLabel, formatLanguage } = useFormat();
|
||||
|
||||
return (
|
||||
<div className="grid place-items-center gap-5">
|
||||
|
@ -54,7 +50,7 @@ export const Credits = ({
|
|||
<h2 className="text-xl">{format("translation_notice")}</h2>
|
||||
<div className="flex flex-wrap place-content-center place-items-center gap-2">
|
||||
<p className="font-headers font-bold">{format("source_language")}:</p>
|
||||
<Chip text={prettyLanguage(sourceLanguageCode, languages)} />
|
||||
<Chip text={formatLanguage(sourceLanguageCode)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -2,11 +2,9 @@ import { Fragment } from "react";
|
|||
import { ToolTip } from "../ToolTip";
|
||||
import { Button } from "./Button";
|
||||
import { cJoin } from "helpers/className";
|
||||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { iterateMap } from "helpers/others";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -32,7 +30,7 @@ export const LanguageSwitcher = ({
|
|||
onLanguageChanged,
|
||||
showBadge = true,
|
||||
}: Props): JSX.Element => {
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
const { formatLanguage } = useFormat();
|
||||
return (
|
||||
<ToolTip
|
||||
content={
|
||||
|
@ -45,7 +43,7 @@ export const LanguageSwitcher = ({
|
|||
onLanguageChanged(value);
|
||||
sendAnalytics("Language Switcher", `Switch language (${locale})`);
|
||||
}}
|
||||
text={prettyLanguage(locale, languages)}
|
||||
text={formatLanguage(locale)}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
|
|
@ -7,7 +7,6 @@ import { TextInput } from "components/Inputs/TextInput";
|
|||
import { Popup } from "components/Containers/Popup";
|
||||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { cJoin, cIf } from "helpers/className";
|
||||
import { prettyLanguage } from "helpers/formatters";
|
||||
import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter, useAtomPair, useAtomSetter } from "helpers/atoms";
|
||||
|
@ -36,8 +35,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
const perfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const isPerfModeToggleable = useAtomGetter(atoms.settings.isPerfModeToggleable);
|
||||
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
const { format } = useFormat();
|
||||
const { format, formatLanguage } = useFormat();
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
|
||||
const is1ColumnLayout = useAtomGetter(atoms.containerQueries.is1ColumnLayout);
|
||||
|
@ -77,7 +75,7 @@ export const SettingsPopup = (): JSX.Element => {
|
|||
<OrderableList
|
||||
items={preferredLanguages.map((locale) => ({
|
||||
code: locale,
|
||||
name: prettyLanguage(locale, languages),
|
||||
name: formatLanguage(locale),
|
||||
}))}
|
||||
insertLabels={[
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { MouseEventHandler, useCallback } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Markdown } from "./Markdown/Markdown";
|
||||
import { Chip } from "components/Chip";
|
||||
import { Ico } from "components/Ico";
|
||||
|
@ -7,13 +6,14 @@ import { Img } from "components/Img";
|
|||
import { UpPressable } from "components/Containers/UpPressable";
|
||||
import { DatePickerFragment, PricePickerFragment, UploadImageFragment } from "graphql/generated";
|
||||
import { cIf, cJoin } from "helpers/className";
|
||||
import { prettyDate, prettyDuration, prettyPrice, prettyShortenNumber } from "helpers/formatters";
|
||||
import { prettyDuration, prettyShortenNumber } from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
||||
import { useSmartLanguage } from "hooks/useSmartLanguage";
|
||||
import { TranslatedProps } from "types/TranslatedProps";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -77,11 +77,10 @@ export const PreviewCard = ({
|
|||
disabled = false,
|
||||
onClick,
|
||||
}: Props): JSX.Element => {
|
||||
const { formatPrice, formatDate } = useFormat();
|
||||
const isPerfModeEnabled = useAtomGetter(atoms.settings.isPerfModeEnabled);
|
||||
const currency = useAtomGetter(atoms.settings.currency);
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
const preferredCurrency = useAtomGetter(atoms.settings.currency);
|
||||
const isHoverable = useDeviceSupportsHover();
|
||||
const router = useRouter();
|
||||
|
||||
const metadataJSX = (
|
||||
<>
|
||||
|
@ -90,13 +89,13 @@ export const PreviewCard = ({
|
|||
{metadata.releaseDate && (
|
||||
<p className="text-sm">
|
||||
<Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyDate(metadata.releaseDate, router.locale)}
|
||||
{formatDate(metadata.releaseDate)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.price && (
|
||||
<p className="justify-self-end text-sm">
|
||||
<Ico icon="shopping_cart" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyPrice(metadata.price, currencies, currency, router.locale)}
|
||||
{formatPrice(metadata.price, preferredCurrency)}
|
||||
</p>
|
||||
)}
|
||||
{metadata.views && (
|
||||
|
|
|
@ -4,6 +4,8 @@ import { readFileSync, writeFileSync } from "fs";
|
|||
import { config } from "dotenv";
|
||||
import { getReadySdk } from "./sdk";
|
||||
import {
|
||||
LocalDataGetCurrenciesQuery,
|
||||
LocalDataGetLanguagesQuery,
|
||||
LocalDataGetTypesTranslationsQuery,
|
||||
LocalDataGetWebsiteInterfacesQuery,
|
||||
} from "./generated";
|
||||
|
@ -12,6 +14,10 @@ import {
|
|||
Langui,
|
||||
TypesTranslations,
|
||||
processTypesTranslations,
|
||||
Currencies,
|
||||
processCurrencies,
|
||||
Languages,
|
||||
processLanguages,
|
||||
} from "helpers/localData";
|
||||
import { getLogger } from "helpers/logger";
|
||||
|
||||
|
@ -86,3 +92,13 @@ export const getTypesTranslations = (): TypesTranslations => {
|
|||
const typesTranslations = readLocalData<LocalDataGetTypesTranslationsQuery>("typesTranslations");
|
||||
return processTypesTranslations(typesTranslations);
|
||||
};
|
||||
|
||||
export const getCurrencies = (): Currencies => {
|
||||
const currencies = readLocalData<LocalDataGetCurrenciesQuery>("currencies");
|
||||
return processCurrencies(currencies);
|
||||
};
|
||||
|
||||
export const getLanguages = (): Languages => {
|
||||
const languages = readLocalData<LocalDataGetLanguagesQuery>("languages");
|
||||
return processLanguages(languages);
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GetStaticProps } from "next";
|
|||
import { getReadySdk } from "./sdk";
|
||||
import { PostWithTranslations } from "types/types";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
import { prettyDate, prettySlug } from "helpers/formatters";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { getDefaultPreferredLanguages, staticSmartLanguage } from "helpers/locales";
|
||||
import { filterHasAttributes } from "helpers/asserts";
|
||||
import { getDescription } from "helpers/description";
|
||||
|
@ -17,7 +17,7 @@ export const getPostStaticProps =
|
|||
(slug: string): GetStaticProps =>
|
||||
async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const { format, formatCategory } = getFormat(context.locale);
|
||||
const { format, formatCategory, formatDate } = getFormat(context.locale);
|
||||
const post = await sdk.getPost({
|
||||
slug: slug,
|
||||
});
|
||||
|
@ -35,7 +35,7 @@ export const getPostStaticProps =
|
|||
const title = selectedTranslation?.title ?? prettySlug(slug);
|
||||
|
||||
const description = getDescription(selectedTranslation?.excerpt ?? selectedTranslation?.body, {
|
||||
[format("release_date")]: [prettyDate(post.posts.data[0].attributes.date, context.locale)],
|
||||
[format("release_date")]: [formatDate(post.posts.data[0].attributes.date)],
|
||||
[format("category", { count: Infinity })]: filterHasAttributes(
|
||||
post.posts.data[0].attributes.categories?.data,
|
||||
["attributes"]
|
||||
|
|
|
@ -1,43 +1,7 @@
|
|||
import { convert } from "html-to-text";
|
||||
import { sanitize } from "isomorphic-dompurify";
|
||||
import { marked } from "marked";
|
||||
import { convertPrice } from "./numbers";
|
||||
import { isDefinedAndNotEmpty, isUndefined } from "./asserts";
|
||||
import { datePickerToDate } from "./date";
|
||||
import { Currencies, Languages } from "./localData";
|
||||
import { DatePickerFragment, PricePickerFragment } from "graphql/generated";
|
||||
|
||||
export const prettyDate = (
|
||||
datePicker: DatePickerFragment,
|
||||
locale = "en",
|
||||
dateStyle: Intl.DateTimeFormatOptions["dateStyle"] = "medium"
|
||||
): string => datePickerToDate(datePicker).toLocaleString(locale, { dateStyle });
|
||||
|
||||
export const prettyPrice = (
|
||||
pricePicker: PricePickerFragment,
|
||||
currencies: Currencies,
|
||||
targetCurrencyCode?: string,
|
||||
locale = "en"
|
||||
): string => {
|
||||
if (!targetCurrencyCode) return "";
|
||||
if (isUndefined(pricePicker.amount)) return "";
|
||||
|
||||
const targetCurrency = currencies.find(
|
||||
(currency) => currency.attributes?.code === targetCurrencyCode
|
||||
);
|
||||
|
||||
if (targetCurrency?.attributes) {
|
||||
const amountInTargetCurrency = convertPrice(pricePicker, targetCurrency);
|
||||
return amountInTargetCurrency.toLocaleString(locale, {
|
||||
style: "currency",
|
||||
currency: targetCurrency.attributes.code,
|
||||
});
|
||||
}
|
||||
return pricePicker.amount.toLocaleString(locale, {
|
||||
style: "currency",
|
||||
currency: pricePicker.currency?.data?.attributes?.code,
|
||||
});
|
||||
};
|
||||
import { isDefinedAndNotEmpty } from "./asserts";
|
||||
|
||||
export const prettySlug = (slug?: string, parentSlug?: string): string => {
|
||||
let newSlug = slug;
|
||||
|
@ -94,14 +58,6 @@ export const prettyDuration = (seconds: number): string => {
|
|||
return result;
|
||||
};
|
||||
|
||||
export const prettyLanguage = (code: string, languages: Languages): string => {
|
||||
let result = code;
|
||||
languages.forEach((language) => {
|
||||
if (language.attributes?.code === code) result = language.attributes.localized_name;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export const prettyURL = (url: string): string => {
|
||||
const domain = new URL(url);
|
||||
return domain.hostname.replace("www.", "");
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
import { IntlMessageFormat } from "intl-messageformat";
|
||||
import { LibraryItemMetadataDynamicZone } from "graphql/generated";
|
||||
import {
|
||||
DatePickerFragment,
|
||||
LibraryItemMetadataDynamicZone,
|
||||
PricePickerFragment,
|
||||
} from "graphql/generated";
|
||||
import { ICUParams } from "graphql/icuParams";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { getLangui, getTypesTranslations } from "graphql/fetchLocalData";
|
||||
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/asserts";
|
||||
import {
|
||||
getCurrencies,
|
||||
getLanguages,
|
||||
getLangui,
|
||||
getTypesTranslations,
|
||||
} from "graphql/fetchLocalData";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { LibraryItemMetadata } from "types/types";
|
||||
import { datePickerToDate } from "helpers/date";
|
||||
import { convertPrice } from "helpers/numbers";
|
||||
|
||||
type WordingKey = keyof ICUParams;
|
||||
type LibraryItemType = Exclude<LibraryItemMetadataDynamicZone["__typename"], undefined>;
|
||||
|
@ -45,10 +56,18 @@ export const getFormat = (
|
|||
formatContentType: (slug: string) => string;
|
||||
formatWikiTag: (slug: string) => string;
|
||||
formatWeaponType: (slug: string) => string;
|
||||
formatLanguage: (code: string) => string;
|
||||
formatPrice: (price: PricePickerFragment, targetCurrencyCode?: string) => string;
|
||||
formatDate: (
|
||||
datePicker: DatePickerFragment,
|
||||
dateStyle?: Intl.DateTimeFormatOptions["dateStyle"]
|
||||
) => string;
|
||||
} => {
|
||||
const langui = getLangui(locale);
|
||||
const fallbackLangui = getLangui("en");
|
||||
const typesTranslations = getTypesTranslations();
|
||||
const currencies = getCurrencies();
|
||||
const languages = getLanguages();
|
||||
|
||||
const format = (
|
||||
key: WordingKey,
|
||||
|
@ -214,6 +233,35 @@ export const getFormat = (
|
|||
return findTranslation(locale) ?? findTranslation("en") ?? prettySlug(slug);
|
||||
};
|
||||
|
||||
const formatLanguage = (code: string) =>
|
||||
languages.find((language) => language.attributes?.code === code)?.attributes?.localized_name ??
|
||||
code.toUpperCase();
|
||||
|
||||
const formatPrice = (price: PricePickerFragment, targetCurrencyCode?: string) => {
|
||||
if (isUndefined(price.amount)) return "";
|
||||
|
||||
const targetCurrency = currencies.find(
|
||||
(currency) => currency.attributes?.code === targetCurrencyCode
|
||||
);
|
||||
|
||||
if (targetCurrency?.attributes) {
|
||||
const amountInTargetCurrency = convertPrice(price, targetCurrency);
|
||||
return amountInTargetCurrency.toLocaleString(locale, {
|
||||
style: "currency",
|
||||
currency: targetCurrency.attributes.code,
|
||||
});
|
||||
}
|
||||
return price.amount.toLocaleString(locale, {
|
||||
style: "currency",
|
||||
currency: price.currency?.data?.attributes?.code,
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = (
|
||||
datePicker: DatePickerFragment,
|
||||
dateStyle: Intl.DateTimeFormatOptions["dateStyle"] = "medium"
|
||||
): string => datePickerToDate(datePicker).toLocaleString(locale, { dateStyle });
|
||||
|
||||
return {
|
||||
format,
|
||||
formatLibraryItemType,
|
||||
|
@ -224,5 +272,8 @@ export const getFormat = (
|
|||
formatContentType,
|
||||
formatWikiTag,
|
||||
formatWeaponType,
|
||||
formatLanguage,
|
||||
formatPrice,
|
||||
formatDate,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,12 +3,18 @@ import { useCallback } from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { useAtomGetter } from "helpers/atoms";
|
||||
import { LibraryItemMetadataDynamicZone } from "graphql/generated";
|
||||
import {
|
||||
DatePickerFragment,
|
||||
LibraryItemMetadataDynamicZone,
|
||||
PricePickerFragment,
|
||||
} from "graphql/generated";
|
||||
import { ICUParams } from "graphql/icuParams";
|
||||
import { isDefined, isDefinedAndNotEmpty } from "helpers/asserts";
|
||||
import { isDefined, isDefinedAndNotEmpty, isUndefined } from "helpers/asserts";
|
||||
import { getLogger } from "helpers/logger";
|
||||
import { prettySlug } from "helpers/formatters";
|
||||
import { LibraryItemMetadata } from "types/types";
|
||||
import { convertPrice } from "helpers/numbers";
|
||||
import { datePickerToDate } from "helpers/date";
|
||||
|
||||
const logger = getLogger("🗺️ [I18n]");
|
||||
|
||||
|
@ -69,10 +75,18 @@ export const useFormat = (): {
|
|||
formatContentType: (slug: string) => string;
|
||||
formatWikiTag: (slug: string) => string;
|
||||
formatWeaponType: (slug: string) => string;
|
||||
formatLanguage: (code: string) => string;
|
||||
formatPrice: (price: PricePickerFragment, targetCurrencyCode?: string) => string;
|
||||
formatDate: (
|
||||
datePicker: DatePickerFragment,
|
||||
dateStyle?: Intl.DateTimeFormatOptions["dateStyle"]
|
||||
) => string;
|
||||
} => {
|
||||
const langui = useAtomGetter(atoms.localData.langui);
|
||||
const fallbackLangui = useAtomGetter(atoms.localData.fallbackLangui);
|
||||
const typesTranslations = useAtomGetter(atoms.localData.typesTranslations);
|
||||
const languages = useAtomGetter(atoms.localData.languages);
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
const { locale = "en" } = useRouter();
|
||||
|
||||
const format = useCallback(
|
||||
|
@ -280,6 +294,44 @@ Falling back to en translation.`
|
|||
[locale, typesTranslations.weaponTypes]
|
||||
);
|
||||
|
||||
const formatLanguage = useCallback(
|
||||
(code: string) =>
|
||||
languages.find((language) => language.attributes?.code === code)?.attributes
|
||||
?.localized_name ?? code.toUpperCase(),
|
||||
[languages]
|
||||
);
|
||||
|
||||
const formatPrice = useCallback(
|
||||
(price: PricePickerFragment, targetCurrencyCode?: string) => {
|
||||
if (isUndefined(price.amount)) return "";
|
||||
|
||||
const targetCurrency = currencies.find(
|
||||
(currency) => currency.attributes?.code === targetCurrencyCode
|
||||
);
|
||||
|
||||
if (targetCurrency?.attributes) {
|
||||
const amountInTargetCurrency = convertPrice(price, targetCurrency);
|
||||
return amountInTargetCurrency.toLocaleString(locale, {
|
||||
style: "currency",
|
||||
currency: targetCurrency.attributes.code,
|
||||
});
|
||||
}
|
||||
return price.amount.toLocaleString(locale, {
|
||||
style: "currency",
|
||||
currency: price.currency?.data?.attributes?.code,
|
||||
});
|
||||
},
|
||||
[currencies, locale]
|
||||
);
|
||||
|
||||
const formatDate = useCallback(
|
||||
(
|
||||
datePicker: DatePickerFragment,
|
||||
dateStyle: Intl.DateTimeFormatOptions["dateStyle"] = "medium"
|
||||
) => datePickerToDate(datePicker).toLocaleString(locale, { dateStyle }),
|
||||
[locale]
|
||||
);
|
||||
|
||||
return {
|
||||
format,
|
||||
formatLibraryItemType,
|
||||
|
@ -290,5 +342,8 @@ Falling back to en translation.`
|
|||
formatContentType,
|
||||
formatWikiTag,
|
||||
formatWeaponType,
|
||||
formatLanguage,
|
||||
formatPrice,
|
||||
formatDate,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useCallback } from "react";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { HorizontalLine } from "components/HorizontalLine";
|
||||
|
@ -12,7 +11,7 @@ import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/Cont
|
|||
import { SubPanel } from "components/Containers/SubPanel";
|
||||
import { GetVideoQuery } from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
|
||||
import { prettyShortenNumber } from "helpers/formatters";
|
||||
import { filterHasAttributes, isDefined } from "helpers/asserts";
|
||||
import { getVideoFile, getVideoThumbnailURL } from "helpers/videos";
|
||||
import { getOpenGraph } from "helpers/openGraph";
|
||||
|
@ -37,8 +36,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
const isContentPanelAtLeast4xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast4xl);
|
||||
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
|
||||
const { format } = useFormat();
|
||||
const router = useRouter();
|
||||
const { format, formatDate } = useFormat();
|
||||
|
||||
const subPanel = (
|
||||
<SubPanel>
|
||||
|
@ -85,7 +83,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => {
|
|||
<div className="flex w-full flex-row flex-wrap place-items-center gap-x-6">
|
||||
<p>
|
||||
<Ico icon="event" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
{prettyDate(video.published_date, router.locale)}
|
||||
{formatDate(video.published_date)}
|
||||
</p>
|
||||
<p>
|
||||
<Ico icon="visibility" className="mr-1 translate-y-[.15em] !text-base" />
|
||||
|
|
|
@ -42,12 +42,14 @@ const DEFAULT_FILTERS_STATE = {
|
|||
keepInfoVisible: true,
|
||||
query: "",
|
||||
page: 1,
|
||||
lang: 0,
|
||||
};
|
||||
|
||||
const queryParamSchema = z.object({
|
||||
query: z.coerce.string().optional(),
|
||||
page: z.coerce.number().positive().optional(),
|
||||
sort: z.coerce.number().min(0).max(5).optional(),
|
||||
lang: z.coerce.number().min(0).optional(),
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -59,7 +61,7 @@ interface Props extends AppLayoutRequired {}
|
|||
|
||||
const Contents = (props: Props): JSX.Element => {
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const { format, formatCategory, formatContentType } = useFormat();
|
||||
const { format, formatCategory, formatContentType, formatLanguage } = useFormat();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||
|
||||
|
@ -72,6 +74,17 @@ const Contents = (props: Props): JSX.Element => {
|
|||
[format]
|
||||
);
|
||||
|
||||
const languageOptions = useMemo(() => {
|
||||
const memo =
|
||||
router.locales?.map((language) => ({
|
||||
meiliAttribute: language,
|
||||
displayedName: formatLanguage(language),
|
||||
})) ?? [];
|
||||
|
||||
memo.unshift({ meiliAttribute: "", displayedName: format("all") });
|
||||
return memo;
|
||||
}, [router.locales, formatLanguage, format]);
|
||||
|
||||
const [sortingMethod, setSortingMethod] = useState<number>(
|
||||
router.query.sort ?? DEFAULT_FILTERS_STATE.sortingMethod
|
||||
);
|
||||
|
@ -82,25 +95,41 @@ const Contents = (props: Props): JSX.Element => {
|
|||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const [contents, setContents] = useState<CustomSearchResponse<MeiliContent>>();
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
const [languageOption, setLanguageOption] = useState(
|
||||
router.query.lang ?? DEFAULT_FILTERS_STATE.lang
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
const currentSortingMethod = sortingMethods[sortingMethod];
|
||||
const currentSortingMethod = sortingMethods[sortingMethod]?.meiliAttribute;
|
||||
const currentLanguageOption = languageOptions[languageOption]?.meiliAttribute;
|
||||
|
||||
const filter: string[] = [];
|
||||
if (languageOption !== 0) {
|
||||
filter.push(`filterable_languages = ${currentLanguageOption}`);
|
||||
}
|
||||
|
||||
const searchResult = await meiliSearch(MeiliIndices.CONTENT, query, {
|
||||
attributesToRetrieve: ["translations", "id", "slug", "categories", "type", "thumbnail"],
|
||||
attributesToHighlight: ["translations"],
|
||||
attributesToCrop: ["translations.displayable_description"],
|
||||
filter,
|
||||
hitsPerPage: 25,
|
||||
page,
|
||||
sort: isDefined(currentSortingMethod) ? [currentSortingMethod.meiliAttribute] : undefined,
|
||||
sort: isDefined(currentSortingMethod) ? [currentSortingMethod] : undefined,
|
||||
});
|
||||
setContents(filterHitsWithHighlight<MeiliContent>(searchResult, "translations"));
|
||||
|
||||
setContents(
|
||||
languageOption === 0
|
||||
? filterHitsWithHighlight<MeiliContent>(searchResult, "translations")
|
||||
: searchResult
|
||||
);
|
||||
};
|
||||
fetchPosts();
|
||||
}, [query, page, sortingMethod, sortingMethods]);
|
||||
}, [query, page, sortingMethod, sortingMethods, languageOption, languageOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady)
|
||||
|
@ -108,15 +137,17 @@ const Contents = (props: Props): JSX.Element => {
|
|||
page,
|
||||
query,
|
||||
sort: sortingMethod,
|
||||
lang: languageOption,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, query, sortingMethod, router.isReady]);
|
||||
}, [page, query, languageOption, sortingMethod, router.isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (isDefined(router.query.page)) setPage(router.query.page);
|
||||
if (isDefined(router.query.query)) setQuery(router.query.query);
|
||||
if (isDefined(router.query.sort)) setSortingMethod(router.query.sort);
|
||||
if (isDefined(router.query.lang)) setLanguageOption(router.query.lang);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady]);
|
||||
|
@ -173,6 +204,24 @@ const Contents = (props: Props): JSX.Element => {
|
|||
/>
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel label={format("language", { count: Infinity })}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={languageOptions.map((item) => item.displayedName)}
|
||||
value={languageOption}
|
||||
onChange={(newLanguageOption) => {
|
||||
setPage(1);
|
||||
setLanguageOption(newLanguageOption);
|
||||
sendAnalytics(
|
||||
"Contents/All",
|
||||
`Change language filter (${
|
||||
languageOptions.map((item) => item.meiliAttribute)[newLanguageOption]
|
||||
})`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
|
@ -190,10 +239,11 @@ const Contents = (props: Props): JSX.Element => {
|
|||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
setPage(DEFAULT_FILTERS_STATE.page);
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setSortingMethod(DEFAULT_FILTERS_STATE.sortingMethod);
|
||||
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
setLanguageOption(DEFAULT_FILTERS_STATE.lang);
|
||||
sendAnalytics("Contents/All", "Reset all filters");
|
||||
}}
|
||||
/>
|
||||
|
@ -212,13 +262,19 @@ const Contents = (props: Props): JSX.Element => {
|
|||
href={`/contents/${item.slug}`}
|
||||
translations={filterHasAttributes(item._formatted.translations, [
|
||||
"language.data.attributes.code",
|
||||
]).map(({ displayable_description, language, ...otherAttributes }) => ({
|
||||
...otherAttributes,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: undefined,
|
||||
language: language.data.attributes.code,
|
||||
}))}
|
||||
])
|
||||
.map(({ displayable_description, language, ...otherAttributes }) => ({
|
||||
...otherAttributes,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: undefined,
|
||||
language: language.data.attributes.code,
|
||||
}))
|
||||
.filter(
|
||||
({ language }) =>
|
||||
languageOption === 0 ||
|
||||
language === languageOptions[languageOption]?.meiliAttribute
|
||||
)}
|
||||
fallback={{ title: prettySlug(item.slug) }}
|
||||
thumbnail={item.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Fragment, useCallback, useMemo } from "react";
|
||||
import { GetStaticPaths, GetStaticPathsResult, GetStaticProps } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { Chip } from "components/Chip";
|
||||
|
@ -20,13 +19,7 @@ import {
|
|||
GetLibraryItemQuery,
|
||||
} from "graphql/generated";
|
||||
import { getReadySdk } from "graphql/sdk";
|
||||
import {
|
||||
prettyDate,
|
||||
prettyInlineTitle,
|
||||
prettyPrice,
|
||||
prettySlug,
|
||||
prettyURL,
|
||||
} from "helpers/formatters";
|
||||
import { prettyInlineTitle, prettySlug, prettyURL } from "helpers/formatters";
|
||||
import { ImageQuality } from "helpers/img";
|
||||
import { convertMmToInch } from "helpers/numbers";
|
||||
import { sortRangedContent } from "helpers/others";
|
||||
|
@ -96,15 +89,15 @@ const LibrarySlug = ({
|
|||
formatCategory,
|
||||
formatContentType,
|
||||
formatLibraryItemSubType,
|
||||
formatPrice,
|
||||
formatDate,
|
||||
} = useFormat();
|
||||
const currencies = useAtomGetter(atoms.localData.currencies);
|
||||
|
||||
const isContentPanelAtLeast3xl = useAtomGetter(atoms.containerQueries.isContentPanelAtLeast3xl);
|
||||
const isContentPanelAtLeastSm = useAtomGetter(atoms.containerQueries.isContentPanelAtLeastSm);
|
||||
const is3ColumnsLayout = useAtomGetter(atoms.containerQueries.is3ColumnsLayout);
|
||||
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useRouter();
|
||||
const { value: keepInfoVisible, toggle: toggleKeepInfoVisible } = useBoolean(false);
|
||||
|
||||
const { showLightBox } = useAtomGetter(atoms.lightBox);
|
||||
|
@ -320,24 +313,17 @@ const LibrarySlug = ({
|
|||
{item.release_date && (
|
||||
<div className="grid place-content-start place-items-center">
|
||||
<h3 className="text-xl">{format("release_date")}</h3>
|
||||
<p>{prettyDate(item.release_date, router.locale)}</p>
|
||||
<p>{formatDate(item.release_date)}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.price && (
|
||||
<div className="grid place-content-start place-items-center text-center">
|
||||
<h3 className="text-xl">{format("price")}</h3>
|
||||
<p>
|
||||
{prettyPrice(
|
||||
item.price,
|
||||
currencies,
|
||||
item.price.currency?.data?.attributes?.code,
|
||||
router.locale
|
||||
)}
|
||||
</p>
|
||||
<p>{formatPrice(item.price)}</p>
|
||||
{item.price.currency?.data?.attributes?.code !== currency && (
|
||||
<p>
|
||||
{prettyPrice(item.price, currencies, currency, router.locale)}
|
||||
{formatPrice(item.price, currency)}
|
||||
<br />({format("calculated").toLowerCase()})
|
||||
</p>
|
||||
)}
|
||||
|
@ -627,7 +613,9 @@ export default LibrarySlug;
|
|||
|
||||
export const getStaticProps: GetStaticProps = async (context) => {
|
||||
const sdk = getReadySdk();
|
||||
const { format, formatCategory, formatLibraryItemSubType } = getFormat(context.locale);
|
||||
const { format, formatCategory, formatLibraryItemSubType, formatDate } = getFormat(
|
||||
context.locale
|
||||
);
|
||||
const item = await sdk.getLibraryItem({
|
||||
slug: context.params && isDefined(context.params.slug) ? context.params.slug.toString() : "",
|
||||
});
|
||||
|
@ -648,7 +636,7 @@ export const getStaticProps: GetStaticProps = async (context) => {
|
|||
: [],
|
||||
[format("release_date")]: [
|
||||
item.libraryItems.data[0].attributes.release_date
|
||||
? prettyDate(item.libraryItems.data[0].attributes.release_date, context.locale)
|
||||
? formatDate(item.libraryItems.data[0].attributes.release_date)
|
||||
: undefined,
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { z } from "zod";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
|
@ -31,6 +31,7 @@ import { prettySlug } from "helpers/formatters";
|
|||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -41,11 +42,13 @@ const DEFAULT_FILTERS_STATE = {
|
|||
query: "",
|
||||
keepInfoVisible: true,
|
||||
page: 1,
|
||||
lang: 0,
|
||||
};
|
||||
|
||||
const queryParamSchema = z.object({
|
||||
query: z.coerce.string().optional(),
|
||||
page: z.coerce.number().positive().optional(),
|
||||
lang: z.coerce.number().min(0).optional(),
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -56,9 +59,10 @@ const queryParamSchema = z.object({
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const News = ({ ...otherProps }: Props): JSX.Element => {
|
||||
const { format, formatCategory } = useFormat();
|
||||
const { format, formatCategory, formatLanguage } = useFormat();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
|
||||
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
|
||||
|
@ -68,13 +72,32 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const isTerminalMode = useAtomGetter(atoms.layout.terminalMode);
|
||||
const languageOptions = useMemo(() => {
|
||||
const memo =
|
||||
router.locales?.map((language) => ({
|
||||
meiliAttribute: language,
|
||||
displayedName: formatLanguage(language),
|
||||
})) ?? [];
|
||||
|
||||
memo.unshift({ meiliAttribute: "", displayedName: format("all") });
|
||||
return memo;
|
||||
}, [router.locales, formatLanguage, format]);
|
||||
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const [posts, setPosts] = useState<CustomSearchResponse<MeiliPost>>();
|
||||
const [languageOption, setLanguageOption] = useState(
|
||||
router.query.lang ?? DEFAULT_FILTERS_STATE.lang
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
const currentLanguageOption = languageOptions[languageOption]?.meiliAttribute;
|
||||
const filter = ["hidden = false"];
|
||||
|
||||
if (languageOption !== 0) {
|
||||
filter.push(`filterable_languages = ${currentLanguageOption}`);
|
||||
}
|
||||
|
||||
const searchResult = await meiliSearch(MeiliIndices.POST, query, {
|
||||
hitsPerPage: 25,
|
||||
page,
|
||||
|
@ -82,26 +105,33 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
attributesToHighlight: ["translations.title", "translations.displayable_description"],
|
||||
attributesToCrop: ["translations.displayable_description"],
|
||||
sort: ["sortable_date:desc"],
|
||||
filter: ["hidden = false"],
|
||||
filter,
|
||||
});
|
||||
setPosts(filterHitsWithHighlight<MeiliPost>(searchResult, "translations"));
|
||||
|
||||
setPosts(
|
||||
languageOption === 0
|
||||
? filterHitsWithHighlight<MeiliPost>(searchResult, "translations")
|
||||
: searchResult
|
||||
);
|
||||
};
|
||||
fetchPosts();
|
||||
}, [query, page]);
|
||||
}, [query, page, languageOption, languageOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady)
|
||||
router.updateQuery({
|
||||
page,
|
||||
query,
|
||||
lang: languageOption,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, query, router.isReady]);
|
||||
}, [page, query, languageOption, router.isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (isDefined(router.query.page)) setPage(router.query.page);
|
||||
if (isDefined(router.query.query)) setQuery(router.query.query);
|
||||
if (isDefined(router.query.lang)) setLanguageOption(router.query.lang);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady]);
|
||||
|
@ -131,6 +161,24 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={format("language", { count: Infinity })}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={languageOptions.map((item) => item.displayedName)}
|
||||
value={languageOption}
|
||||
onChange={(newLanguageOption) => {
|
||||
setPage(1);
|
||||
setLanguageOption(newLanguageOption);
|
||||
sendAnalytics(
|
||||
"News",
|
||||
`Change language filter (${
|
||||
languageOptions.map((item) => item.meiliAttribute)[newLanguageOption]
|
||||
})`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
|
@ -148,8 +196,10 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(DEFAULT_FILTERS_STATE.page);
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
setLanguageOption(DEFAULT_FILTERS_STATE.lang);
|
||||
sendAnalytics("News", "Reset all filters");
|
||||
}}
|
||||
/>
|
||||
|
@ -168,13 +218,19 @@ const News = ({ ...otherProps }: Props): JSX.Element => {
|
|||
href={`/news/${item.slug}`}
|
||||
translations={filterHasAttributes(item._formatted.translations, [
|
||||
"language.data.attributes.code",
|
||||
]).map(({ excerpt, displayable_description, language, ...otherAttributes }) => ({
|
||||
...otherAttributes,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: excerpt,
|
||||
language: language.data.attributes.code,
|
||||
}))}
|
||||
])
|
||||
.map(({ excerpt, displayable_description, language, ...otherAttributes }) => ({
|
||||
...otherAttributes,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: excerpt,
|
||||
language: language.data.attributes.code,
|
||||
}))
|
||||
.filter(
|
||||
({ language }) =>
|
||||
languageOption === 0 ||
|
||||
language === languageOptions[languageOption]?.meiliAttribute
|
||||
)}
|
||||
fallback={{ title: prettySlug(item.slug) }}
|
||||
thumbnail={item.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="3/2"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { z } from "zod";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
|
@ -20,12 +20,18 @@ import { TranslatedPreviewCard } from "components/PreviewCard";
|
|||
import { sendAnalytics } from "helpers/analytics";
|
||||
import { useTypedRouter } from "hooks/useTypedRouter";
|
||||
import { MeiliIndices, MeiliWikiPage } from "shared/meilisearch-graphql-typings/meiliTypes";
|
||||
import { containsHighlight, CustomSearchResponse, meiliSearch } from "helpers/search";
|
||||
import {
|
||||
containsHighlight,
|
||||
CustomSearchResponse,
|
||||
filterHitsWithHighlight,
|
||||
meiliSearch,
|
||||
} from "helpers/search";
|
||||
import { Paginator } from "components/Containers/Paginator";
|
||||
import { useFormat } from "hooks/useFormat";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
import { useAtomSetter } from "helpers/atoms";
|
||||
import { atoms } from "contexts/atoms";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -36,11 +42,13 @@ const DEFAULT_FILTERS_STATE = {
|
|||
query: "",
|
||||
keepInfoVisible: true,
|
||||
page: 1,
|
||||
lang: 0,
|
||||
};
|
||||
|
||||
const queryParamSchema = z.object({
|
||||
query: z.coerce.string().optional(),
|
||||
page: z.coerce.number().positive().optional(),
|
||||
lang: z.coerce.number().min(0).optional(),
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -51,24 +59,44 @@ const queryParamSchema = z.object({
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const Wiki = (props: Props): JSX.Element => {
|
||||
const { format, formatCategory, formatWikiTag } = useFormat();
|
||||
const { format, formatCategory, formatWikiTag, formatLanguage } = useFormat();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const setSubPanelOpened = useAtomSetter(atoms.layout.subPanelOpened);
|
||||
const closeSubPanel = useCallback(() => setSubPanelOpened(false), [setSubPanelOpened]);
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
|
||||
const languageOptions = useMemo(() => {
|
||||
const memo =
|
||||
router.locales?.map((language) => ({
|
||||
meiliAttribute: language,
|
||||
displayedName: formatLanguage(language),
|
||||
})) ?? [];
|
||||
|
||||
memo.unshift({ meiliAttribute: "", displayedName: format("all") });
|
||||
return memo;
|
||||
}, [router.locales, formatLanguage, format]);
|
||||
|
||||
const [wikiPages, setWikiPages] = useState<CustomSearchResponse<MeiliWikiPage>>();
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const {
|
||||
value: keepInfoVisible,
|
||||
toggle: toggleKeepInfoVisible,
|
||||
setValue: setKeepInfoVisible,
|
||||
} = useBoolean(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const [wikiPages, setWikiPages] = useState<CustomSearchResponse<MeiliWikiPage>>();
|
||||
const [languageOption, setLanguageOption] = useState(
|
||||
router.query.lang ?? DEFAULT_FILTERS_STATE.lang
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchWikiPages = async () => {
|
||||
const currentLanguageOption = languageOptions[languageOption]?.meiliAttribute;
|
||||
|
||||
const filter: string[] = [];
|
||||
if (languageOption !== 0) {
|
||||
filter.push(`filterable_languages = ${currentLanguageOption}`);
|
||||
}
|
||||
|
||||
const searchResult = await meiliSearch(MeiliIndices.WIKI_PAGE, query, {
|
||||
hitsPerPage: 25,
|
||||
page,
|
||||
|
@ -79,25 +107,32 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
"translations.displayable_description",
|
||||
],
|
||||
attributesToCrop: ["translations.displayable_description"],
|
||||
filter,
|
||||
});
|
||||
setWikiPages(searchResult);
|
||||
setWikiPages(
|
||||
languageOption === 0
|
||||
? filterHitsWithHighlight<MeiliWikiPage>(searchResult, "translations")
|
||||
: searchResult
|
||||
);
|
||||
};
|
||||
fetchWikiPages();
|
||||
}, [query, page]);
|
||||
}, [query, page, languageOptions, languageOption]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady)
|
||||
router.updateQuery({
|
||||
page,
|
||||
query,
|
||||
lang: languageOption,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, query, router.isReady]);
|
||||
}, [page, query, router.isReady, languageOption]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (isDefined(router.query.page)) setPage(router.query.page);
|
||||
if (isDefined(router.query.query)) setQuery(router.query.query);
|
||||
if (isDefined(router.query.lang)) setLanguageOption(router.query.lang);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady]);
|
||||
|
@ -127,6 +162,24 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={format("language", { count: Infinity })}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={languageOptions.map((item) => item.displayedName)}
|
||||
value={languageOption}
|
||||
onChange={(newLanguageOption) => {
|
||||
setPage(1);
|
||||
setLanguageOption(newLanguageOption);
|
||||
sendAnalytics(
|
||||
"Wiki",
|
||||
`Change language filter (${
|
||||
languageOptions.map((item) => item.meiliAttribute)[newLanguageOption]
|
||||
})`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
|
@ -144,9 +197,10 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(1);
|
||||
setPage(DEFAULT_FILTERS_STATE.page);
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
setLanguageOption(DEFAULT_FILTERS_STATE.lang);
|
||||
sendAnalytics("Wiki", "Reset all filters");
|
||||
}}
|
||||
/>
|
||||
|
@ -182,19 +236,31 @@ const Wiki = (props: Props): JSX.Element => {
|
|||
href={`/wiki/${item.slug}`}
|
||||
translations={filterHasAttributes(item._formatted.translations, [
|
||||
"language.data.attributes.code",
|
||||
]).map(
|
||||
({ aliases, summary, displayable_description, language, ...otherAttributes }) => ({
|
||||
...otherAttributes,
|
||||
subtitle:
|
||||
aliases && aliases.length > 0
|
||||
? aliases.map((alias) => alias?.alias).join("・")
|
||||
: undefined,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: summary,
|
||||
language: language.data.attributes.code,
|
||||
})
|
||||
)}
|
||||
])
|
||||
.map(
|
||||
({
|
||||
aliases,
|
||||
summary,
|
||||
displayable_description,
|
||||
language,
|
||||
...otherAttributes
|
||||
}) => ({
|
||||
...otherAttributes,
|
||||
subtitle:
|
||||
aliases && aliases.length > 0
|
||||
? aliases.map((alias) => alias?.alias).join("・")
|
||||
: undefined,
|
||||
description: containsHighlight(displayable_description)
|
||||
? displayable_description
|
||||
: summary,
|
||||
language: language.data.attributes.code,
|
||||
})
|
||||
)
|
||||
.filter(
|
||||
({ language }) =>
|
||||
languageOption === 0 ||
|
||||
language === languageOptions[languageOption]?.meiliAttribute
|
||||
)}
|
||||
fallback={{ title: prettySlug(item.slug) }}
|
||||
thumbnail={item.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio={"4/3"}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GetStaticProps } from "next";
|
||||
import { z } from "zod";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { AppLayout, AppLayoutRequired } from "components/AppLayout";
|
||||
import { getFormat } from "helpers/i18n";
|
||||
|
@ -29,6 +29,7 @@ import { useDeviceSupportsHover } from "hooks/useMediaQuery";
|
|||
import { WithLabel } from "components/Inputs/WithLabel";
|
||||
import { Switch } from "components/Inputs/Switch";
|
||||
import { ReturnButton } from "components/PanelComponents/ReturnButton";
|
||||
import { Select } from "components/Inputs/Select";
|
||||
|
||||
/*
|
||||
* ╭─────────────╮
|
||||
|
@ -39,11 +40,13 @@ const DEFAULT_FILTERS_STATE = {
|
|||
query: "",
|
||||
keepInfoVisible: true,
|
||||
page: 1,
|
||||
lang: 0,
|
||||
};
|
||||
|
||||
const queryParamSchema = z.object({
|
||||
query: z.coerce.string().optional(),
|
||||
page: z.coerce.number().positive().optional(),
|
||||
lang: z.coerce.number().min(0).optional(),
|
||||
});
|
||||
|
||||
/*
|
||||
|
@ -54,12 +57,26 @@ const queryParamSchema = z.object({
|
|||
interface Props extends AppLayoutRequired {}
|
||||
|
||||
const Weapons = (props: Props): JSX.Element => {
|
||||
const { format, formatCategory, formatWeaponType } = useFormat();
|
||||
const { format, formatCategory, formatWeaponType, formatLanguage } = useFormat();
|
||||
const hoverable = useDeviceSupportsHover();
|
||||
const router = useTypedRouter(queryParamSchema);
|
||||
|
||||
const languageOptions = useMemo(() => {
|
||||
const memo =
|
||||
router.locales?.map((language) => ({
|
||||
meiliAttribute: language,
|
||||
displayedName: formatLanguage(language),
|
||||
})) ?? [];
|
||||
|
||||
memo.unshift({ meiliAttribute: "", displayedName: format("all") });
|
||||
return memo;
|
||||
}, [router.locales, formatLanguage, format]);
|
||||
|
||||
const [query, setQuery] = useState(router.query.query ?? DEFAULT_FILTERS_STATE.query);
|
||||
const [page, setPage] = useState<number>(router.query.page ?? DEFAULT_FILTERS_STATE.page);
|
||||
const [languageOption, setLanguageOption] = useState(
|
||||
router.query.lang ?? DEFAULT_FILTERS_STATE.lang
|
||||
);
|
||||
|
||||
const {
|
||||
value: keepInfoVisible,
|
||||
|
@ -71,6 +88,13 @@ const Weapons = (props: Props): JSX.Element => {
|
|||
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
const currentLanguageOption = languageOptions[languageOption]?.meiliAttribute;
|
||||
|
||||
const filter: string[] = [];
|
||||
if (languageOption !== 0) {
|
||||
filter.push(`filterable_languages = ${currentLanguageOption}`);
|
||||
}
|
||||
|
||||
const searchResult = await meiliSearch(MeiliIndices.WEAPON, query, {
|
||||
hitsPerPage: 25,
|
||||
page,
|
||||
|
@ -78,25 +102,32 @@ const Weapons = (props: Props): JSX.Element => {
|
|||
attributesToHighlight: ["translations.description", "translations.names"],
|
||||
attributesToCrop: ["translations.description"],
|
||||
sort: ["slug:asc"],
|
||||
filter,
|
||||
});
|
||||
setWeapons(filterHitsWithHighlight<MeiliWeapon>(searchResult, "translations"));
|
||||
setWeapons(
|
||||
languageOption === 0
|
||||
? filterHitsWithHighlight<MeiliWeapon>(searchResult, "translations")
|
||||
: searchResult
|
||||
);
|
||||
};
|
||||
fetchPosts();
|
||||
}, [query, page]);
|
||||
}, [query, page, languageOptions, languageOption]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady)
|
||||
router.updateQuery({
|
||||
page,
|
||||
query,
|
||||
lang: languageOption,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [page, query, router.isReady]);
|
||||
}, [page, query, languageOption, router.isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (isDefined(router.query.page)) setPage(router.query.page);
|
||||
if (isDefined(router.query.query)) setQuery(router.query.query);
|
||||
if (isDefined(router.query.lang)) setLanguageOption(router.query.lang);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady]);
|
||||
|
@ -132,6 +163,24 @@ const Weapons = (props: Props): JSX.Element => {
|
|||
}}
|
||||
/>
|
||||
|
||||
<WithLabel label={format("language", { count: Infinity })}>
|
||||
<Select
|
||||
className="w-full"
|
||||
options={languageOptions.map((item) => item.displayedName)}
|
||||
value={languageOption}
|
||||
onChange={(newLanguageOption) => {
|
||||
setPage(1);
|
||||
setLanguageOption(newLanguageOption);
|
||||
sendAnalytics(
|
||||
"Weapons",
|
||||
`Change language filter (${
|
||||
languageOptions.map((item) => item.meiliAttribute)[newLanguageOption]
|
||||
})`
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
{hoverable && (
|
||||
<WithLabel label={format("always_show_info")}>
|
||||
<Switch
|
||||
|
@ -149,8 +198,10 @@ const Weapons = (props: Props): JSX.Element => {
|
|||
text={format("reset_all_filters")}
|
||||
icon="settings_backup_restore"
|
||||
onClick={() => {
|
||||
setPage(DEFAULT_FILTERS_STATE.page);
|
||||
setQuery(DEFAULT_FILTERS_STATE.query);
|
||||
setKeepInfoVisible(DEFAULT_FILTERS_STATE.keepInfoVisible);
|
||||
setLanguageOption(DEFAULT_FILTERS_STATE.lang);
|
||||
sendAnalytics("Weapons", "Reset all filters");
|
||||
}}
|
||||
/>
|
||||
|
@ -176,12 +227,18 @@ const Weapons = (props: Props): JSX.Element => {
|
|||
href={`/wiki/weapons/${item.slug}`}
|
||||
translations={filterHasAttributes(item._formatted.translations, [
|
||||
"language.data.attributes.code",
|
||||
]).map(({ description, language, names: [primaryName, ...aliases] }) => ({
|
||||
language: language.data.attributes.code,
|
||||
title: primaryName,
|
||||
subtitle: aliases.join("・"),
|
||||
description: containsHighlight(description) ? description : undefined,
|
||||
}))}
|
||||
])
|
||||
.map(({ description, language, names: [primaryName, ...aliases] }) => ({
|
||||
language: language.data.attributes.code,
|
||||
title: primaryName,
|
||||
subtitle: aliases.join("・"),
|
||||
description: containsHighlight(description) ? description : undefined,
|
||||
}))
|
||||
.filter(
|
||||
({ language }) =>
|
||||
languageOption === 0 ||
|
||||
language === languageOptions[languageOption]?.meiliAttribute
|
||||
)}
|
||||
fallback={{ title: prettySlug(item.slug) }}
|
||||
thumbnail={item.thumbnail?.data?.attributes}
|
||||
thumbnailAspectRatio="1/1"
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface MeiliContent
|
|||
displayable_description?: string | null;
|
||||
})[];
|
||||
sortable_updated_date: number;
|
||||
filterable_languages: string[];
|
||||
}
|
||||
|
||||
export interface MeiliVideo extends VideoAttributesFragment {
|
||||
|
@ -50,6 +51,7 @@ export interface MeiliPost extends Omit<PostAttributesFragment, "translations">
|
|||
> & {
|
||||
displayable_description?: string | null;
|
||||
})[];
|
||||
filterable_languages: string[];
|
||||
}
|
||||
|
||||
export interface MeiliWikiPage extends Omit<WikiPageAttributesFragment, "translations"> {
|
||||
|
@ -60,6 +62,7 @@ export interface MeiliWikiPage extends Omit<WikiPageAttributesFragment, "transla
|
|||
> & {
|
||||
displayable_description?: string | null;
|
||||
})[];
|
||||
filterable_languages: string[];
|
||||
}
|
||||
|
||||
type WeaponAttributesTranslation = NonNullable<
|
||||
|
@ -79,6 +82,7 @@ export interface MeiliWeapon extends Omit<WeaponAttributesFragment, "name" | "st
|
|||
description: string;
|
||||
language: WeaponAttributesTranslation["language"];
|
||||
}[];
|
||||
filterable_languages: string[];
|
||||
}
|
||||
|
||||
export enum MeiliIndices {
|
||||
|
|
Loading…
Reference in New Issue