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