Improved stuff

This commit is contained in:
DrMint 2022-06-18 21:53:23 +02:00
parent 6ae54c39d4
commit bc0764c0d0
40 changed files with 1074 additions and 963 deletions

View File

@ -27,7 +27,7 @@ module.exports = {
},
{
source: "/gallery",
destination: "https://gallery.accords-library.com/",
destination: "https://gallery.accords-library.com/posts",
permanent: false,
},
];

View File

@ -5,7 +5,13 @@ import { AppStaticProps } from "graphql/getAppStaticProps";
import { cIf, cJoin } from "helpers/className";
import { prettyLanguage, prettySlug } from "helpers/formatters";
import { getOgImage, ImageQuality } from "helpers/img";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import {
filterHasAttributes,
isDefined,
isDefinedAndNotEmpty,
isUndefined,
iterateMap,
} from "helpers/others";
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
import { useMediaMobile } from "hooks/useMediaQuery";
@ -19,6 +25,7 @@ import { ButtonGroup } from "./Inputs/ButtonGroup";
import { OrderableList } from "./Inputs/OrderableList";
import { Select } from "./Inputs/Select";
import { TextInput } from "./Inputs/TextInput";
import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder";
import { MainPanel } from "./Panels/MainPanel";
import { Popup } from "./Popup";
@ -98,7 +105,7 @@ export function AppLayout(props: Props): JSX.Element {
if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return;
if (mainPanelOpen === true) {
setMainPanelOpen(false);
} else if (subPanel === true && contentPanel === true) {
} else if (isDefined(subPanel) && isDefined(contentPanel)) {
setSubPanelOpen(true);
}
}
@ -116,7 +123,7 @@ export function AppLayout(props: Props): JSX.Element {
});
const turnSubIntoContent = useMemo(
() => isDefined(subPanel) && isDefined(contentPanel),
() => isDefined(subPanel) && isUndefined(contentPanel),
[contentPanel, subPanel]
);
@ -173,11 +180,8 @@ export function AppLayout(props: Props): JSX.Element {
const currencyOptions = useMemo(() => {
const list: string[] = [];
currencies.map((currentCurrency) => {
if (
currentCurrency.attributes &&
isDefinedAndNotEmpty(currentCurrency.attributes.code)
)
filterHasAttributes(currencies).map((currentCurrency) => {
if (isDefinedAndNotEmpty(currentCurrency.attributes.code))
list.push(currentCurrency.attributes.code);
});
return list;
@ -283,15 +287,10 @@ export function AppLayout(props: Props): JSX.Element {
{isDefined(contentPanel) ? (
contentPanel
) : (
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl
border-2 border-dotted border-dark p-8 text-dark opacity-40"
>
<p className="text-4xl"></p>
<p className="w-64 text-2xl">{langui.select_option_sidebar}</p>
</div>
</div>
<ContentPlaceholder
message={langui.select_option_sidebar ?? ""}
icon={Icon.ChevronLeft}
/>
)}
</div>
@ -299,11 +298,10 @@ export function AppLayout(props: Props): JSX.Element {
{isDefined(subPanel) && (
<div
className={cJoin(
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
border-black bg-light transition-transform duration-300 [grid-area:sub]
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
mobile:justify-self-end mobile:border-r-0 mobile:border-l-[1px]
mobile:[grid-area:content]`,
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
transition-transform duration-300 [grid-area:sub] [scrollbar-width:none]
webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%] mobile:justify-self-end
mobile:border-r-0 mobile:border-l-[1px] mobile:[grid-area:content]`,
turnSubIntoContent
? "mobile:w-full mobile:border-l-0"
: subPanelOpen === true
@ -318,10 +316,10 @@ export function AppLayout(props: Props): JSX.Element {
{/* Main panel */}
<div
className={cJoin(
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
border-black bg-light transition-transform duration-300 [grid-area:main]
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
mobile:justify-self-start mobile:[grid-area:content]`,
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dark/50 bg-light
transition-transform duration-300 [grid-area:main] [scrollbar-width:none]
webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%] mobile:justify-self-start
mobile:[grid-area:content]`,
cIf(mainPanelOpen === false, "mobile:-translate-x-full")
)}
>
@ -399,8 +397,9 @@ export function AppLayout(props: Props): JSX.Element {
])
}
onChange={(items) => {
const newPreferredLanguages = [...items].map(
([code]) => code
const newPreferredLanguages = iterateMap(
items,
(code) => code
);
setPreferredLanguages(newPreferredLanguages);
if (router.locale !== newPreferredLanguages[0]) {

View File

@ -63,9 +63,8 @@ export function Button(props: Props): JSX.Element {
text-dark transition-all`,
cIf(
active,
"!border-black bg-black text-light drop-shadow-black-lg",
`cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg
active:border-black active:bg-black active:text-light active:drop-shadow-black-lg`
"!border-black bg-black !text-light drop-shadow-black-lg",
"cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg"
),
className
)}

View File

@ -2,6 +2,7 @@ import { Icon } from "components/Ico";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className";
import { prettyLanguage } from "helpers/formatters";
import { iterateMap } from "helpers/others";
import { Fragment } from "react";
import { ToolTip } from "../ToolTip";
@ -22,15 +23,13 @@ export function LanguageSwitcher(props: Props): JSX.Element {
<ToolTip
content={
<div className={cJoin("flex flex-col gap-2", className)}>
{[...locales].map(([locale, value], index) => (
{iterateMap(locales, (locale, value, index) => (
<Fragment key={index}>
{locale && (
<Button
active={value === localesIndex}
onClick={() => onLanguageChanged(value)}
text={prettyLanguage(locale, props.languages)}
/>
)}
</Fragment>
))}
</div>

View File

@ -1,5 +1,5 @@
import { Ico, Icon } from "components/Ico";
import { arrayMove, isDefinedAndNotEmpty } from "helpers/others";
import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others";
import { Fragment, useCallback, useState } from "react";
@ -16,17 +16,16 @@ export function OrderableList(props: Props): JSX.Element {
const updateOrder = useCallback(
(sourceIndex: number, targetIndex: number) => {
const newItems = arrayMove([...items], sourceIndex, targetIndex);
const map = new Map(newItems);
setItems(map);
onChange?.(map);
const newItems = mapMoveEntry(items, sourceIndex, targetIndex);
setItems(newItems);
onChange?.(newItems);
},
[items, onChange]
);
return (
<div className="grid gap-2">
{[...items].map(([key, value], index) => (
{iterateMap(items, (key, value, index) => (
<Fragment key={key}>
{props.insertLabels &&
isDefinedAndNotEmpty(props.insertLabels.get(index)) && (

View File

@ -43,7 +43,7 @@ export function Select(props: Props): JSX.Element {
className="!text-xs"
onClick={() => {
setState(-1);
toggleOpened();
setOpened(false);
}}
/>
)}

View File

@ -11,7 +11,7 @@ interface Props {
}
export function Switch(props: Props): JSX.Element {
const { state, setState, className, disabled } = props;
const { state, setState, className, disabled = false } = props;
const toggleState = useToggle(setState);
return (
<div
@ -26,7 +26,7 @@ export function Switch(props: Props): JSX.Element {
className
)}
onClick={() => {
if (disabled === false) toggleState();
if (!disabled) toggleState();
}}
>
<div

View File

@ -3,7 +3,9 @@ import { Ico, Icon } from "components/Ico";
import { Button } from "components/Inputs/Button";
import { GetLibraryItemQuery } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cIf, cJoin } from "helpers/className";
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
import { filterHasAttributes } from "helpers/others";
import { useToggle } from "hooks/useToggle";
import { useState } from "react";
@ -29,9 +31,10 @@ export function ContentLine(props: Props): JSX.Element {
if (content.attributes) {
return (
<div
className={`grid gap-2 rounded-lg px-4 ${
opened && "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade"
}`}
className={cJoin(
"grid gap-2 rounded-lg px-4",
cIf(opened, "my-2 h-auto bg-mid py-3 shadow-inner-sm shadow-shade")
)}
>
<div
className="grid grid-cols-[auto_auto_1fr_auto_12ch] place-items-center
@ -52,11 +55,11 @@ export function ContentLine(props: Props): JSX.Element {
</h3>
</a>
<div className="flex flex-row flex-wrap gap-1">
{content.attributes.content?.data?.attributes?.categories?.data.map(
(category) => (
<Chip key={category.id}>{category.attributes?.short}</Chip>
)
)}
{filterHasAttributes(
content.attributes.content?.data?.attributes?.categories?.data
).map((category) => (
<Chip key={category.id}>{category.attributes.short}</Chip>
))}
</div>
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
<p>

View File

@ -7,10 +7,15 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img";
import { isInteger } from "helpers/numbers";
import { getStatusDescription, isDefinedAndNotEmpty } from "helpers/others";
import {
filterHasAttributes,
getStatusDescription,
isDefined,
isDefinedAndNotEmpty,
} from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment } from "react";
import { Fragment, useMemo } from "react";
interface Props {
openLightBox: (images: string[], index?: number) => void;
@ -81,9 +86,14 @@ export function ScanSet(props: Props): JSX.Element {
},
});
const pages = useMemo(
() => selectedScan && filterHasAttributes(selectedScan.pages?.data),
[selectedScan]
);
return (
<>
{selectedScan && (
{selectedScan && isDefined(pages) && (
<div>
<div
className="flex flex-row flex-wrap place-items-center
@ -126,16 +136,16 @@ export function ScanSet(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Scanners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedScan.scanners.data.map((scanner) => (
{filterHasAttributes(selectedScan.scanners.data).map(
(scanner) => (
<Fragment key={scanner.id}>
{scanner.attributes && (
<RecorderChip
langui={langui}
recorder={scanner.attributes}
/>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}
@ -144,7 +154,8 @@ export function ScanSet(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedScan.cleaners.data.map((cleaner) => (
{filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => (
<Fragment key={cleaner.id}>
{cleaner.attributes && (
<RecorderChip
@ -153,7 +164,8 @@ export function ScanSet(props: Props): JSX.Element {
/>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}
@ -163,7 +175,8 @@ export function ScanSet(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedScan.typesetters.data.map((typesetter) => (
{filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => (
<Fragment key={typesetter.id}>
{typesetter.attributes && (
<RecorderChip
@ -172,7 +185,8 @@ export function ScanSet(props: Props): JSX.Element {
/>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}
@ -188,18 +202,15 @@ export function ScanSet(props: Props): JSX.Element {
className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] mobile:grid-cols-2"
>
{selectedScan.pages?.data.map((page, index) => (
{pages.map((page, index) => (
<div
key={page.id}
className="cursor-pointer transition-transform
drop-shadow-shade-lg hover:scale-[1.02]"
onClick={() => {
const images: string[] = [];
selectedScan.pages?.data.map((image) => {
if (
image.attributes &&
isDefinedAndNotEmpty(image.attributes.url)
)
pages.map((image) => {
if (isDefinedAndNotEmpty(image.attributes.url))
images.push(
getAssetURL(image.attributes.url, ImageQuality.Large)
);

View File

@ -8,7 +8,7 @@ import {
} from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getAssetURL, ImageQuality } from "helpers/img";
import { getStatusDescription } from "helpers/others";
import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment } from "react";
@ -87,16 +87,16 @@ export function ScanSetCover(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Scanners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedScan.scanners.data.map((scanner) => (
{filterHasAttributes(selectedScan.scanners.data).map(
(scanner) => (
<Fragment key={scanner.id}>
{scanner.attributes && (
<RecorderChip
langui={langui}
recorder={scanner.attributes}
/>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}
@ -105,16 +105,16 @@ export function ScanSetCover(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedScan.cleaners.data.map((cleaner) => (
{filterHasAttributes(selectedScan.cleaners.data).map(
(cleaner) => (
<Fragment key={cleaner.id}>
{cleaner.attributes && (
<RecorderChip
langui={langui}
recorder={cleaner.attributes}
/>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}
@ -124,16 +124,16 @@ export function ScanSetCover(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedScan.typesetters.data.map((typesetter) => (
{filterHasAttributes(selectedScan.typesetters.data).map(
(typesetter) => (
<Fragment key={typesetter.id}>
{typesetter.attributes && (
<RecorderChip
langui={langui}
recorder={typesetter.attributes}
/>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}

View File

@ -0,0 +1,30 @@
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { isDefined } from "helpers/others";
interface Props {
message: string;
icon?: Icon;
}
export function ContentPlaceholder(props: Props): JSX.Element {
const { message, icon } = props;
return (
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl border-2 border-dotted
border-dark p-8 text-dark opacity-40"
>
{isDefined(icon) && <Ico icon={icon} className="!text-[300%]" />}
<p
className={cJoin(
"w-64 text-2xl",
cIf(!isDefined(icon), "text-center")
)}
>
{message}
</p>
</div>
</div>
);
}

View File

@ -17,7 +17,15 @@ interface Props {
}
export function NavOption(props: Props): JSX.Element {
const { url, icon, title, subtitle, border, reduced, onClick } = props;
const {
url,
icon,
title,
subtitle,
border = false,
reduced = false,
onClick,
} = props;
const router = useRouter();
const isActive = useMemo(
() => router.asPath.startsWith(url),
@ -36,7 +44,7 @@ export function NavOption(props: Props): JSX.Element {
}
placement="right"
className="text-left"
disabled={reduced === false}
disabled={!reduced}
>
<div
onClick={(event) => {
@ -63,7 +71,7 @@ export function NavOption(props: Props): JSX.Element {
>
{icon && <Ico icon={icon} className="mt-[-.1em] !text-2xl" />}
{reduced === false && (
{!reduced && (
<div>
<h3 className="text-2xl">{title}</h3>
{isDefinedAndNotEmpty(subtitle) && (

View File

@ -15,10 +15,10 @@ export function ContentPanel(props: Props): JSX.Element {
const { width = ContentPanelWidthSizes.Default, children } = props;
return (
<div className={`grid px-4 pt-10 pb-20 desktop:py-20 desktop:px-10`}>
<div className={`grid h-full px-4 desktop:px-10`}>
<main
className={cJoin(
"place-self-center",
"justify-self-center pt-10 pb-20 desktop:pt-20 desktop:pb-32",
width === ContentPanelWidthSizes.Default
? "max-w-2xl"
: width === ContentPanelWidthSizes.Large

View File

@ -60,7 +60,7 @@ export function MainPanel(props: Props): JSX.Element {
</Link>
{(!mainPanelReduced || !isDesktop) && (
<h2 className="text-3xl">Accord&rsquo;s Library</h2>
<h2 className="mb-4 text-3xl">Accord&rsquo;s Library</h2>
)}
<div
@ -155,7 +155,7 @@ export function MainPanel(props: Props): JSX.Element {
*/}
<NavOption
url="https://gallery.accords-library.com/"
url="https://gallery.accords-library.com/posts/"
icon={Icon.Collections}
title={langui.gallery}
reduced={mainPanelReduced && isDesktop}

View File

@ -28,7 +28,7 @@ export function Popup(props: Props): JSX.Element {
const { setMenuGestures } = useAppLayout();
useEffect(() => {
setMenuGestures(state);
setMenuGestures(!state);
}, [setMenuGestures, state]);
return (

View File

@ -1,7 +1,7 @@
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getDescription } from "helpers/description";
import { prettySlug } from "helpers/formatters";
import { getStatusDescription } from "helpers/others";
import { filterHasAttributes, getStatusDescription } from "helpers/others";
import { PostWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import { Fragment, useMemo } from "react";
@ -102,14 +102,12 @@ export function PostPage(props: Props): JSX.Element {
<div>
<p className="font-headers">{"Authors"}:</p>
<div className="grid place-content-center place-items-center gap-2">
{post.authors.data.map((author) => (
{filterHasAttributes(post.authors.data).map((author) => (
<Fragment key={author.id}>
{author.attributes && (
<RecorderChip
langui={langui}
recorder={author.attributes}
/>
)}
</Fragment>
))}
</div>

View File

@ -244,7 +244,8 @@ export function PreviewCard(props: Props): JSX.Element {
cIf(
!keepInfoVisible,
`-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute
hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md`
hoverable:drop-shadow-shade-lg notHoverable:opacity-100
notHoverable:rounded-b-md`
)
)}
>
@ -302,9 +303,7 @@ interface TranslatedProps
languages: AppStaticProps["languages"];
}
export function TranslatedPreviewCard(
props: Immutable<TranslatedProps>
): JSX.Element {
export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element {
const {
translations = [{ title: props.slug, language: "default" }],
slug,

View File

@ -89,7 +89,7 @@ interface TranslatedProps
}
export function TranslatedPreviewLine(
props: Immutable<TranslatedProps>
props: TranslatedProps
): JSX.Element {
const {
translations = [{ title: props.slug, language: "default" }],

View File

@ -2,6 +2,7 @@ import { Chip } from "components/Chip";
import { RecorderChipFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others";
import { Fragment } from "react";
import { Img } from "./Img";
@ -33,13 +34,13 @@ export function RecorderChip(props: Props): JSX.Element {
{recorder.languages?.data && recorder.languages.data.length > 0 && (
<div className="flex flex-row flex-wrap gap-1">
<p>{langui.languages}:</p>
{recorder.languages.data.map((language) => (
<Fragment key={language.attributes?.code}>
{language.attributes && (
{filterHasAttributes(recorder.languages.data).map(
(language) => (
<Fragment key={language.attributes.code}>
<Chip>{language.attributes.code.toUpperCase()}</Chip>
)}
</Fragment>
))}
)
)}
</div>
)}
{recorder.pronouns && (

View File

@ -6,6 +6,7 @@ import { GetContentTextQuery, UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { filterHasAttributes } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
@ -89,9 +90,11 @@ export function ThumbnailHeader(props: Props): JSX.Element {
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{categories.data.map((category) => (
<Chip key={category.id}>{category.attributes?.name}</Chip>
))}
{filterHasAttributes(categories.data).map(
(category) => (
<Chip key={category.id}>{category.attributes.name}</Chip>
)
)}
</div>
</div>
)}

View File

@ -6,7 +6,11 @@ import {
GetChronologyItemsQuery,
} from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getStatusDescription } from "helpers/others";
import {
filterDefined,
filterHasAttributes,
getStatusDescription,
} from "helpers/others";
import { Fragment } from "react";
@ -44,13 +48,16 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
</p>
<div className="col-start-2 row-span-2 row-start-1 grid gap-4">
{props.item.attributes.events?.map((event) => (
<Fragment key={event?.id}>
{event && (
{props.item.attributes.events &&
filterHasAttributes(props.item.attributes.events, [
"id",
"translations",
]).map((event) => (
<Fragment key={event.id}>
<div className="m-0">
{event.translations?.map((translation, translationIndex) => (
{filterDefined(event.translations).map(
(translation, translationIndex) => (
<Fragment key={translationIndex}>
{translation && (
<Fragment>
<div
className="grid
@ -94,9 +101,9 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
""
)}
</Fragment>
)}
</Fragment>
))}
)
)}
<p className="mt-1 grid grid-flow-col gap-1 place-self-start text-xs text-dark">
{event.source?.data ? (
@ -109,7 +116,6 @@ export function ChronologyItemComponent(props: Props): JSX.Element {
)}
</p>
</div>
)}
</Fragment>
))}
</div>

View File

@ -3,7 +3,7 @@ import { GetLibraryItemsPreviewQuery } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { prettyinlineTitle, prettyDate } from "./formatters";
import { convertPrice } from "./numbers";
import { isDefined } from "./others";
import { isDefined, mapRemoveEmptyValues } from "./others";
import { LibraryItemUserStatus } from "./types";
type Items = NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
type GroupLibraryItems = Map<string, Items>;
@ -13,67 +13,67 @@ export function getGroups(
groupByType: number,
items: Items
): GroupLibraryItems {
const groups: GroupLibraryItems = new Map();
switch (groupByType) {
case 0: {
const typeGroup = new Map();
typeGroup.set("Drakengard 1", []);
typeGroup.set("Drakengard 1.3", []);
typeGroup.set("Drakengard 2", []);
typeGroup.set("Drakengard 3", []);
typeGroup.set("Drakengard 4", []);
typeGroup.set("NieR Gestalt", []);
typeGroup.set("NieR Replicant", []);
typeGroup.set("NieR Replicant ver.1.22474487139...", []);
typeGroup.set("NieR:Automata", []);
typeGroup.set("NieR Re[in]carnation", []);
typeGroup.set("SINoALICE", []);
typeGroup.set("Voice of Cards", []);
typeGroup.set("Final Fantasy XIV", []);
typeGroup.set("Thou Shalt Not Die", []);
typeGroup.set("Bakuken", []);
typeGroup.set("YoRHa", []);
typeGroup.set("YoRHa Boys", []);
typeGroup.set(langui.no_category, []);
const noCategory = langui.no_category ?? "No category";
groups.set("Drakengard 1", []);
groups.set("Drakengard 1.3", []);
groups.set("Drakengard 2", []);
groups.set("Drakengard 3", []);
groups.set("Drakengard 4", []);
groups.set("NieR Gestalt", []);
groups.set("NieR Replicant", []);
groups.set("NieR Replicant ver.1.22474487139...", []);
groups.set("NieR:Automata", []);
groups.set("NieR Re[in]carnation", []);
groups.set("SINoALICE", []);
groups.set("Voice of Cards", []);
groups.set("Final Fantasy XIV", []);
groups.set("Thou Shalt Not Die", []);
groups.set("Bakuken", []);
groups.set("YoRHa", []);
groups.set("YoRHa Boys", []);
groups.set(noCategory, []);
items.map((item) => {
if (item.attributes?.categories?.data.length === 0) {
typeGroup.get(langui.no_category)?.push(item);
groups.get(noCategory)?.push(item);
} else {
item.attributes?.categories?.data.map((category) => {
typeGroup.get(category.attributes?.name)?.push(item);
groups.get(category.attributes?.name ?? noCategory)?.push(item);
});
}
});
return typeGroup;
break;
}
case 1: {
const group = new Map();
group.set(langui.audio ?? "Audio", []);
group.set(langui.game ?? "Game", []);
group.set(langui.textual ?? "Textual", []);
group.set(langui.video ?? "Video", []);
group.set(langui.other ?? "Other", []);
group.set(langui.group ?? "Group", []);
group.set(langui.no_type ?? "No type", []);
groups.set(langui.audio ?? "Audio", []);
groups.set(langui.game ?? "Game", []);
groups.set(langui.textual ?? "Textual", []);
groups.set(langui.video ?? "Video", []);
groups.set(langui.other ?? "Other", []);
groups.set(langui.group ?? "Group", []);
groups.set(langui.no_type ?? "No type", []);
items.map((item) => {
if (item.attributes?.metadata && item.attributes.metadata.length > 0) {
switch (item.attributes.metadata[0]?.__typename) {
case "ComponentMetadataAudio":
group.get(langui.audio ?? "Audio")?.push(item);
groups.get(langui.audio ?? "Audio")?.push(item);
break;
case "ComponentMetadataGame":
group.get(langui.game ?? "Game")?.push(item);
groups.get(langui.game ?? "Game")?.push(item);
break;
case "ComponentMetadataBooks":
group.get(langui.textual ?? "Textual")?.push(item);
groups.get(langui.textual ?? "Textual")?.push(item);
break;
case "ComponentMetadataVideo":
group.get(langui.video ?? "Video")?.push(item);
groups.get(langui.video ?? "Video")?.push(item);
break;
case "ComponentMetadataOther":
group.get(langui.other ?? "Other")?.push(item);
groups.get(langui.other ?? "Other")?.push(item);
break;
case "ComponentMetadataGroup":
switch (
@ -81,19 +81,19 @@ export function getGroups(
?.slug
) {
case "audio":
group.get(langui.audio ?? "Audio")?.push(item);
groups.get(langui.audio ?? "Audio")?.push(item);
break;
case "video":
group.get(langui.video ?? "Video")?.push(item);
groups.get(langui.video ?? "Video")?.push(item);
break;
case "game":
group.get(langui.game ?? "Game")?.push(item);
groups.get(langui.game ?? "Game")?.push(item);
break;
case "textual":
group.get(langui.textual ?? "Textual")?.push(item);
groups.get(langui.textual ?? "Textual")?.push(item);
break;
case "mixed":
group.get(langui.group ?? "Group")?.push(item);
groups.get(langui.group ?? "Group")?.push(item);
break;
default: {
throw new Error(
@ -107,10 +107,10 @@ export function getGroups(
}
}
} else {
group.get(langui.no_type ?? "No type")?.push(item);
groups.get(langui.no_type ?? "No type")?.push(item);
}
});
return group;
break;
}
case 2: {
@ -121,29 +121,28 @@ export function getGroups(
years.push(item.attributes.release_date.year);
}
});
const group = new Map();
years.sort((a, b) => a - b);
years.map((year) => {
group.set(year.toString(), []);
groups.set(year.toString(), []);
});
group.set(langui.no_year ?? "No year", []);
groups.set(langui.no_year ?? "No year", []);
items.map((item) => {
if (item.attributes?.release_date?.year) {
group.get(item.attributes.release_date.year.toString())?.push(item);
groups.get(item.attributes.release_date.year.toString())?.push(item);
} else {
group.get(langui.no_year ?? "No year")?.push(item);
groups.get(langui.no_year ?? "No year")?.push(item);
}
});
return group;
break;
}
default: {
const group = new Map();
group.set("", items);
return group;
groups.set("", items);
break;
}
}
return mapRemoveEmptyValues(groups);
}
export function filterItems(
@ -155,7 +154,7 @@ export function filterItems(
showSecondaryItems: boolean,
filterUserStatus: LibraryItemUserStatus | undefined
): Items {
return [...items].filter((item) => {
return items.filter((item) => {
if (!showSubitems && !item.attributes?.root_item) return false;
if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) {
return false;
@ -212,7 +211,7 @@ export function sortBy(
): Items {
switch (orderByType) {
case 0:
return [...items].sort((a, b) => {
return items.sort((a, b) => {
const titleA = prettyinlineTitle(
"",
a.attributes?.title,
@ -226,7 +225,7 @@ export function sortBy(
return titleA.localeCompare(titleB);
});
case 1:
return [...items].sort((a, b) => {
return items.sort((a, b) => {
const priceA = a.attributes?.price
? convertPrice(a.attributes.price, currencies[0])
: 99999;
@ -236,7 +235,7 @@ export function sortBy(
return priceA - priceB;
});
case 2:
return [...items].sort((a, b) => {
return items.sort((a, b) => {
const dateA = a.attributes?.release_date
? prettyDate(a.attributes.release_date)
: "9999";

View File

@ -4,6 +4,7 @@ import {
GetLibraryItemScansQuery,
} from "graphql/generated";
import { AppStaticProps } from "../graphql/getAppStaticProps";
import { SelectiveRequiredNonNullable } from "./types";
type SortContentProps =
| NonNullable<
@ -59,11 +60,6 @@ export function getStatusDescription(
}
}
export function arrayMove<T>(arr: T[], old_index: number, new_index: number) {
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr;
}
export function isDefined<T>(t: T): t is NonNullable<T> {
return t !== null && t !== undefined;
}
@ -78,6 +74,51 @@ export function isDefinedAndNotEmpty(
return isDefined(string) && string.length > 0;
}
export function filterDefined<T>(t: T[]): NonNullable<T>[] {
export function filterDefined<T>(t: T[] | undefined | null): NonNullable<T>[] {
if (isUndefined(t)) return [];
return t.filter((item) => isDefined(item)) as NonNullable<T>[];
}
export function filterHasAttributes<T, P extends keyof NonNullable<T>>(
t: T[] | undefined | null,
attributes?: P[]
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] {
if (isUndefined(t)) return [];
return t.filter((item) => {
if (isDefined(item)) {
const attributesToCheck = attributes ?? (Object.keys(item) as P[]);
return attributesToCheck.every((attribute) => isDefined(item[attribute]));
}
return false;
}) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[];
}
export function iterateMap<K, V, U>(
map: Map<K, V>,
callbackfn: (key: K, value: V, index: number) => U
): U[] {
const result: U[] = [];
let index = 0;
for (const [key, value] of map.entries()) {
result.push(callbackfn(key, value, index));
index += 1;
}
return result;
}
export function mapMoveEntry<K, V>(
map: Map<K, V>,
sourceIndex: number,
targetIndex: number
) {
return new Map(arrayMove([...map], sourceIndex, targetIndex));
}
function arrayMove<T>(arr: T[], sourceIndex: number, targetIndex: number) {
arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]);
return arr;
}
export function mapRemoveEmptyValues<K, V>(groups: Map<K, V[]>): Map<K, V[]> {
return new Map([...groups].filter(([_, items]) => items.length > 0));
}

View File

@ -30,9 +30,13 @@ export interface WikiPageWithTranslations
translations: NonNullable<WikiPage["translations"]>;
}
export type RequiredNonNullable<T> = Required<{
[P in keyof T]: NonNullable<T[P]>;
}>;
export type RequiredNonNullable<T> = {
[P in keyof T]-?: NonNullable<T[P]>;
};
export type SelectiveRequiredNonNullable<T, K extends keyof T> = Omit<T, K> & {
[P in K]-?: NonNullable<T[P]>;
};
export enum LibraryItemUserStatus {
None = 0,

View File

@ -1,7 +1,7 @@
import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher";
import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { isDefined } from "helpers/others";
import { filterDefined, isDefined } from "helpers/others";
import { useRouter } from "next/router";
import { useEffect, useMemo, useState } from "react";
@ -39,11 +39,9 @@ export function useSmartLanguage<T>(
const availableLocales = useMemo(() => {
const map = new Map<string, number>();
items.map((elem, index) => {
if (isDefined(elem)) {
filterDefined(items).map((elem, index) => {
const result = languageExtractor(elem);
if (isDefined(result)) map.set(result, index);
}
});
return map;
}, [items, languageExtractor]);

View File

@ -24,7 +24,7 @@ import { Fragment, useState } from "react";
import { Icon } from "components/Ico";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { WithLabel } from "components/Inputs/WithLabel";
import { isDefined } from "helpers/others";
import { filterHasAttributes, isDefined } from "helpers/others";
interface Props extends AppStaticProps {
channel: NonNullable<
@ -74,9 +74,8 @@ export default function Channel(props: Props): JSX.Element {
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2"
>
{channel?.videos?.data.map((video) => (
{filterHasAttributes(channel?.videos?.data).map((video) => (
<Fragment key={video.id}>
{video.attributes && (
<PreviewCard
href={`/archives/videos/v/${video.attributes.uid}`}
title={video.attributes.title}
@ -86,7 +85,7 @@ export default function Channel(props: Props): JSX.Element {
metadata={{
release_date: video.attributes.published_date,
views: video.attributes.views,
author: channel.title,
author: channel?.title,
position: "Top",
}}
hoverlay={{
@ -94,7 +93,6 @@ export default function Channel(props: Props): JSX.Element {
duration: video.attributes.duration,
}}
/>
)}
</Fragment>
))}
</div>
@ -137,9 +135,8 @@ export async function getStaticPaths(
const channels = await sdk.getVideoChannelsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (channels.videoChannels?.data)
channels.videoChannels.data.map((channel) => {
filterHasAttributes(channels.videoChannels.data).map((channel) => {
context.locales?.map((local) => {
if (channel.attributes)
paths.push({
params: { uid: channel.attributes.uid },
locale: local,

View File

@ -18,6 +18,7 @@ import { GetVideosPreviewQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { prettyDate } from "helpers/formatters";
import { filterHasAttributes } from "helpers/others";
import { getVideoThumbnailURL } from "helpers/videos";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { GetStaticPropsContext } from "next";
@ -95,9 +96,8 @@ export default function Videos(props: Props): JSX.Element {
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2
thin:grid-cols-1"
>
{paginatedVideos[page].map((video) => (
{filterHasAttributes(paginatedVideos[page]).map((video) => (
<Fragment key={video.id}>
{video.attributes && (
<PreviewCard
href={`/archives/videos/v/${video.attributes.uid}`}
title={video.attributes.title}
@ -115,7 +115,6 @@ export default function Videos(props: Props): JSX.Element {
duration: video.attributes.duration,
}}
/>
)}
</Fragment>
))}
</div>

View File

@ -18,7 +18,7 @@ import { GetVideoQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { prettyDate, prettyShortenNumber } from "helpers/formatters";
import { isDefined } from "helpers/others";
import { filterHasAttributes, isDefined } from "helpers/others";
import { getVideoFile } from "helpers/videos";
import { useMediaMobile } from "hooks/useMediaQuery";
import {
@ -213,9 +213,8 @@ export async function getStaticPaths(
const videos = await sdk.getVideosSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (videos.videos?.data)
videos.videos.data.map((video) => {
filterHasAttributes(videos.videos.data).map((video) => {
context.locales?.map((local) => {
if (video.attributes)
paths.push({ params: { uid: video.attributes.uid }, locale: local });
});
});

View File

@ -26,7 +26,11 @@ import {
prettySlug,
} from "helpers/formatters";
import { isUntangibleGroupItem } from "helpers/libraryItem";
import { getStatusDescription } from "helpers/others";
import {
filterHasAttributes,
getStatusDescription,
isDefinedAndNotEmpty,
} from "helpers/others";
import { ContentWithTranslations } from "helpers/types";
import { useMediaMobile } from "hooks/useMediaQuery";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
@ -82,19 +86,17 @@ export default function Content(props: Props): JSX.Element {
horizontalLine
/>
{selectedTranslation?.text_set && (
{selectedTranslation?.text_set?.source_language?.data?.attributes
?.code !== undefined && (
<div className="grid gap-5">
<h2 className="text-xl">
{selectedTranslation.text_set.source_language?.data?.attributes
?.code === selectedTranslation.language?.data?.attributes?.code
{selectedTranslation.text_set.source_language.data.attributes
.code === selectedTranslation.language?.data?.attributes?.code
? langui.transcript_notice
: langui.translation_notice}
</h2>
{selectedTranslation.text_set.source_language?.data?.attributes
?.code &&
selectedTranslation.text_set.source_language.data.attributes
.code !==
{selectedTranslation.text_set.source_language.data.attributes.code !==
selectedTranslation.language?.data?.attributes?.code && (
<div className="grid place-items-center gap-2">
<p className="font-headers">{langui.source_language}:</p>
@ -127,18 +129,16 @@ export default function Content(props: Props): JSX.Element {
<div>
<p className="font-headers">{langui.transcribers}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedTranslation.text_set.transcribers.data.map(
(recorder) => (
{filterHasAttributes(
selectedTranslation.text_set.transcribers.data
).map((recorder) => (
<Fragment key={recorder.id}>
{recorder.attributes && (
<RecorderChip
langui={langui}
recorder={recorder.attributes}
/>
)}
</Fragment>
)
)}
))}
</div>
</div>
)}
@ -148,18 +148,16 @@ export default function Content(props: Props): JSX.Element {
<div>
<p className="font-headers">{langui.translators}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedTranslation.text_set.translators.data.map(
(recorder) => (
{filterHasAttributes(
selectedTranslation.text_set.translators.data
).map((recorder) => (
<Fragment key={recorder.id}>
{recorder.attributes && (
<RecorderChip
langui={langui}
recorder={recorder.attributes}
/>
)}
</Fragment>
)
)}
))}
</div>
</div>
)}
@ -169,23 +167,21 @@ export default function Content(props: Props): JSX.Element {
<div>
<p className="font-headers">{langui.proofreaders}:</p>
<div className="grid place-content-center place-items-center gap-2">
{selectedTranslation.text_set.proofreaders.data.map(
(recorder) => (
{filterHasAttributes(
selectedTranslation.text_set.proofreaders.data
).map((recorder) => (
<Fragment key={recorder.id}>
{recorder.attributes && (
<RecorderChip
langui={langui}
recorder={recorder.attributes}
/>
)}
</Fragment>
)
)}
))}
</div>
</div>
)}
{selectedTranslation.text_set.notes && (
{isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (
<div>
<p className="font-headers">{"Notes"}:</p>
<div className="grid place-content-center place-items-center gap-2">
@ -450,9 +446,8 @@ export async function getStaticPaths(
const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
contents.contents?.data.map((item) => {
filterHasAttributes(contents.contents?.data).map((item) => {
context.locales?.map((local) => {
if (item.attributes)
paths.push({
params: { slug: item.attributes.slug },
locale: local,

View File

@ -21,6 +21,12 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { Button } from "components/Inputs/Button";
import { TextInput } from "components/Inputs/TextInput";
import { useMediaHoverable } from "hooks/useMediaQuery";
import {
filterHasAttributes,
iterateMap,
mapRemoveEmptyValues,
} from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
interface Props extends AppStaticProps {
contents: NonNullable<GetContentsQuery["contents"]>["data"];
@ -128,8 +134,18 @@ export default function Contents(props: Props): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{[...groups].map(
([name, items], index) =>
{/* TODO: Add to langui */}
{groups.size === 0 && (
<ContentPlaceholder
message={
"No results. You can try changing or resetting the search parameters."
}
icon={Icon.ChevronLeft}
/>
)}
{iterateMap(
groups,
(name, items, index) =>
items.length > 0 && (
<Fragment key={index}>
{name && (
@ -140,7 +156,10 @@ export default function Contents(props: Props): JSX.Element {
{name}
<Chip>{`${items.reduce((currentSum, item) => {
if (effectiveCombineRelatedContent) {
if (item.attributes?.group?.data?.attributes?.combine) {
if (
item.attributes?.group?.data?.attributes?.combine ===
true
) {
return (
currentSum +
(item.attributes.group.data.attributes.contents?.data
@ -161,9 +180,8 @@ export default function Contents(props: Props): JSX.Element {
className="grid grid-cols-2 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
>
{items.map((item) => (
{filterHasAttributes(items).map((item) => (
<Fragment key={item.id}>
{item.attributes && (
<TranslatedPreviewCard
href={`/contents/${item.attributes.slug}`}
translations={item.attributes.translations?.map(
@ -182,17 +200,18 @@ export default function Contents(props: Props): JSX.Element {
thumbnailForceAspectRatio
stackNumber={
effectiveCombineRelatedContent &&
item.attributes.group?.data?.attributes?.combine
? item.attributes.group.data.attributes.contents
?.data.length
item.attributes.group?.data?.attributes?.combine ===
true
? item.attributes.group.data.attributes.contents?.data
.length
: 0
}
topChips={
item.attributes.type?.data?.attributes
? [
item.attributes.type.data.attributes.titles?.[0]
? item.attributes.type.data.attributes
.titles[0]?.title
? item.attributes.type.data.attributes.titles[0]
?.title
: prettySlug(
item.attributes.type.data.attributes.slug
),
@ -204,7 +223,6 @@ export default function Contents(props: Props): JSX.Element {
)}
keepInfoVisible={keepInfoVisible}
/>
)}
</Fragment>
))}
</div>
@ -252,71 +270,72 @@ function getGroups(
groupByType: number,
items: Props["contents"]
): GroupContentItems {
const groups: GroupContentItems = new Map();
switch (groupByType) {
case 0: {
const group = new Map();
group.set("Drakengard 1", []);
group.set("Drakengard 1.3", []);
group.set("Drakengard 2", []);
group.set("Drakengard 3", []);
group.set("Drakengard 4", []);
group.set("NieR Gestalt", []);
group.set("NieR Replicant", []);
group.set("NieR Replicant ver.1.22474487139...", []);
group.set("NieR:Automata", []);
group.set("NieR Re[in]carnation", []);
group.set("SINoALICE", []);
group.set("Voice of Cards", []);
group.set("Final Fantasy XIV", []);
group.set("Thou Shalt Not Die", []);
group.set("Bakuken", []);
group.set("YoRHa", []);
group.set("YoRHa Boys", []);
group.set(langui.no_category, []);
const noCategory = langui.no_category ?? "No category";
groups.set("Drakengard 1", []);
groups.set("Drakengard 1.3", []);
groups.set("Drakengard 2", []);
groups.set("Drakengard 3", []);
groups.set("Drakengard 4", []);
groups.set("NieR Gestalt", []);
groups.set("NieR Replicant", []);
groups.set("NieR Replicant ver.1.22474487139...", []);
groups.set("NieR:Automata", []);
groups.set("NieR Re[in]carnation", []);
groups.set("SINoALICE", []);
groups.set("Voice of Cards", []);
groups.set("Final Fantasy XIV", []);
groups.set("Thou Shalt Not Die", []);
groups.set("Bakuken", []);
groups.set("YoRHa", []);
groups.set("YoRHa Boys", []);
groups.set(noCategory, []);
items.map((item) => {
if (item.attributes?.categories?.data.length === 0) {
group.get(langui.no_category)?.push(item);
groups.get(noCategory)?.push(item);
} else {
item.attributes?.categories?.data.map((category) => {
group.get(category.attributes?.name)?.push(item);
groups.get(category.attributes?.name ?? noCategory)?.push(item);
});
}
});
return group;
break;
}
case 1: {
const group = new Map();
items.map((item) => {
const noType = langui.no_type ?? "No type";
const type =
item.attributes?.type?.data?.attributes?.titles?.[0]?.title ??
item.attributes?.type?.data?.attributes?.slug
? prettySlug(item.attributes.type.data.attributes.slug)
: langui.no_type;
if (!group.has(type)) group.set(type, []);
group.get(type)?.push(item);
if (!groups.has(type ?? noType)) groups.set(type ?? noType, []);
groups.get(type ?? noType)?.push(item);
});
return group;
break;
}
default: {
const group: GroupContentItems = new Map();
group.set("", items);
return group;
groups.set("", items);
}
}
return mapRemoveEmptyValues(groups);
}
function filterContents(
contents: Immutable<Props["contents"]>,
contents: Props["contents"],
combineRelatedContent: boolean,
searchName: string
): Immutable<Props["contents"]> {
): Props["contents"] {
return contents.filter((content) => {
if (
combineRelatedContent &&
content.attributes?.group?.data?.attributes?.combine &&
content.attributes?.group?.data?.attributes?.combine === true &&
content.attributes.group.data.attributes.contents?.data[0].id !==
content.id
) {

View File

@ -9,6 +9,7 @@ import { ToolTip } from "components/ToolTip";
import { DevGetContentsQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { filterDefined, filterHasAttributes } from "helpers/others";
import { GetStaticPropsContext } from "next";
@ -116,8 +117,7 @@ function testingContent(contents: Props["contents"]): Report {
lines: [],
};
contents.contents?.data.map((content) => {
if (content.attributes) {
filterHasAttributes(contents.contents?.data).map((content) => {
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
@ -188,8 +188,8 @@ function testingContent(contents: Props["contents"]): Report {
} else {
const titleLanguages: string[] = [];
content.attributes.translations?.map((translation, titleIndex) => {
if (translation && content.attributes) {
filterDefined(content.attributes.translations).map(
(translation, titleIndex) => {
if (translation.language?.data?.id) {
if (translation.language.data.id in titleLanguages) {
report.lines.push({
@ -429,8 +429,7 @@ function testingContent(contents: Props["contents"]): Report {
frontendUrl: frontendUrl,
});
}
});
}
);
}
});
return report;

View File

@ -35,7 +35,12 @@ import {
} from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { convertMmToInch } from "helpers/numbers";
import { isDefined, sortContent } from "helpers/others";
import {
filterHasAttributes,
isDefined,
isDefinedAndNotEmpty,
sortContent,
} from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange";
@ -172,7 +177,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
)}
<div className="grid place-items-center text-center">
<h1 className="text-3xl">{item?.title}</h1>
{item?.subtitle && <h2 className="text-2xl">{item.subtitle}</h2>}
{item && isDefinedAndNotEmpty(item.subtitle) && (
<h2 className="text-2xl">{item.subtitle}</h2>
)}
</div>
<PreviewCardCTAs
@ -196,17 +203,13 @@ export default function LibrarySlug(props: Props): JSX.Element {
{item?.urls && item.urls.length ? (
<div className="flex flex-row place-items-center gap-3">
<p>{langui.available_at}</p>
{item.urls
.filter((url) => url)
.map((url, index) => (
{filterHasAttributes(item.urls).map((url, index) => (
<Fragment key={index}>
{url?.url && (
<Button
href={url.url}
target={"_blank"}
text={prettyURL(url.url)}
/>
)}
</Fragment>
))}
</div>
@ -225,26 +228,19 @@ export default function LibrarySlug(props: Props): JSX.Element {
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
gap-8"
>
{item.gallery.data.map((galleryItem, index) => (
{filterHasAttributes(item.gallery.data).map(
(galleryItem, index) => (
<Fragment key={galleryItem.id}>
{galleryItem.attributes && (
<div
className="relative aspect-square cursor-pointer
transition-transform hover:scale-[1.02]"
onClick={() => {
if (item.gallery?.data) {
const images: string[] = [];
item.gallery.data.map((image) => {
if (image.attributes)
images.push(
getAssetURL(
image.attributes.url,
ImageQuality.Large
)
const images: string[] = filterHasAttributes(
item.gallery?.data
).map((image) =>
getAssetURL(image.attributes.url, ImageQuality.Large)
);
});
openLightBox(images, index);
}
}}
>
<Img
@ -253,9 +249,9 @@ export default function LibrarySlug(props: Props): JSX.Element {
image={galleryItem.attributes}
/>
</div>
)}
</Fragment>
))}
)
)}
</div>
</div>
)}
@ -437,9 +433,8 @@ export default function LibrarySlug(props: Props): JSX.Element {
className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]
items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1"
>
{item.subitems.data.map((subitem) => (
{filterHasAttributes(item.subitems.data).map((subitem) => (
<Fragment key={subitem.id}>
{subitem.attributes && subitem.id && (
<PreviewCard
href={`/library/${subitem.attributes.slug}`}
title={subitem.attributes.title}
@ -476,7 +471,6 @@ export default function LibrarySlug(props: Props): JSX.Element {
/>
}
/>
)}
</Fragment>
))}
</div>
@ -548,13 +542,12 @@ export async function getStaticPaths(
const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (libraryItems.libraryItems) {
libraryItems.libraryItems.data.map((item) => {
context.locales?.map((local) => {
paths.push({ params: { slug: item.attributes?.slug }, locale: local });
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes?.slug }, locale: local })
);
});
});
}
return {
paths,
fallback: "blocking",

View File

@ -15,7 +15,7 @@ import { GetLibraryItemScansQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { prettyinlineTitle, prettySlug } from "helpers/formatters";
import { isDefined, sortContent } from "helpers/others";
import { filterHasAttributes, isDefined, sortContent } from "helpers/others";
import { useLightBox } from "hooks/useLightBox";
import {
@ -146,14 +146,12 @@ export async function getStaticPaths(
const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs({});
const paths: GetStaticPathsResult["paths"] = [];
if (libraryItems.libraryItems) {
libraryItems.libraryItems.data.map((item) => {
context.locales?.map((local) => {
if (item.attributes)
paths.push({ params: { slug: item.attributes.slug }, locale: local });
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
});
});
}
return {
paths,
fallback: "blocking",

View File

@ -31,7 +31,13 @@ import {
import { PreviewCard } from "components/PreviewCard";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { ButtonGroup } from "components/Inputs/ButtonGroup";
import { isDefinedAndNotEmpty, isUndefined } from "helpers/others";
import {
filterHasAttributes,
isDefinedAndNotEmpty,
isUndefined,
iterateMap,
} from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
interface Props extends AppStaticProps {
items: NonNullable<GetLibraryItemsPreviewQuery["libraryItems"]>["data"];
@ -234,11 +240,18 @@ export default function Library(props: Props): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Full}>
{[...groups].map(([name, items]) => (
{/* TODO: Add to langui */}
{groups.size === 0 && (
<ContentPlaceholder
message={
"No results. You can try changing or resetting the search parameters."
}
icon={Icon.ChevronLeft}
/>
)}
{iterateMap(groups, (name, items) => (
<Fragment key={name}>
{items.length > 0 && (
<>
{name && (
{isDefinedAndNotEmpty(name) && (
<h2
className="flex flex-row place-items-center gap-2
pb-2 pt-10 text-2xl first-of-type:pt-0"
@ -256,9 +269,8 @@ export default function Library(props: Props): JSX.Element {
last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(13rem,1fr))]
mobile:grid-cols-2 mobile:gap-4"
>
{items.map((item) => (
{filterHasAttributes(items).map((item) => (
<Fragment key={item.id}>
{isDefinedAndNotEmpty(item.id) && item.attributes && (
<PreviewCard
href={`/library/${item.attributes.slug}`}
title={item.attributes.title}
@ -287,20 +299,15 @@ export default function Library(props: Props): JSX.Element {
<PreviewCardCTAs
id={item.id}
displayCTAs={
!isUntangibleGroupItem(
item.attributes.metadata?.[0]
)
!isUntangibleGroupItem(item.attributes.metadata?.[0])
}
langui={langui}
/>
}
/>
)}
</Fragment>
))}
</div>
</>
)}
</Fragment>
))}
</ContentPanel>

View File

@ -5,7 +5,7 @@ import {
PostStaticProps,
} from "graphql/getPostStaticProps";
import { getReadySdk } from "graphql/sdk";
import { isDefined } from "helpers/others";
import { filterHasAttributes, isDefined } from "helpers/others";
import {
GetStaticPathsContext,
@ -48,12 +48,11 @@ export async function getStaticPaths(
const sdk = getReadySdk();
const posts = await sdk.getPostsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (posts.posts)
posts.posts.data.map((item) => {
context.locales?.map((local) => {
if (item.attributes)
paths.push({ params: { slug: item.attributes.slug }, locale: local });
});
filterHasAttributes(posts.posts?.data).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
});
return {
paths,

View File

@ -19,6 +19,7 @@ import { WithLabel } from "components/Inputs/WithLabel";
import { TextInput } from "components/Inputs/TextInput";
import { Button } from "components/Inputs/Button";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterHasAttributes } from "helpers/others";
interface Props extends AppStaticProps {
posts: NonNullable<GetPostsPreviewQuery["posts"]>["data"];
@ -87,9 +88,8 @@ export default function News(props: Props): JSX.Element {
className="grid grid-cols-1 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"
>
{filteredItems.map((post) => (
{filterHasAttributes(filteredItems).map((post) => (
<Fragment key={post.id}>
{post.attributes && (
<PreviewCard
href={`/news/${post.attributes.slug}`}
title={
@ -109,7 +109,6 @@ export default function News(props: Props): JSX.Element {
position: "Top",
}}
/>
)}
</Fragment>
))}
</div>
@ -145,19 +144,17 @@ export async function getStaticProps(
}
function sortPosts(posts: Props["posts"]): Props["posts"] {
const sortedPosts = [...posts];
sortedPosts
return posts
.sort((a, b) => {
const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999";
const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999";
return dateA.localeCompare(dateB);
})
.reverse();
return sortedPosts;
}
function filterItems(posts: Props["posts"], searchName: string) {
return [...posts].filter((post) => {
return posts.filter((post) => {
if (searchName.length > 1) {
if (
post.attributes?.translations?.[0]?.title

View File

@ -14,7 +14,11 @@ import { SubPanel } from "components/Panels/SubPanel";
import DefinitionCard from "components/Wiki/DefinitionCard";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { isDefined, isDefinedAndNotEmpty } from "helpers/others";
import {
filterHasAttributes,
isDefined,
isDefinedAndNotEmpty,
} from "helpers/others";
import { WikiPageWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import {
@ -89,20 +93,24 @@ export default function WikiPage(props: Props): JSX.Element {
</div>
)}
{page.definitions?.map((definition, index) => (
{filterHasAttributes(page.definitions, ["translations"]).map(
(definition, index) => (
<DefinitionCard
key={index}
source={definition?.source?.data?.attributes?.name}
translations={definition?.translations?.map((translation) => ({
language: translation?.language?.data?.attributes?.code,
definition: translation?.definition,
status: translation?.status,
}))}
source={definition.source?.data?.attributes?.name}
translations={filterHasAttributes(definition.translations).map(
(translation) => ({
language: translation.language.data?.attributes?.code,
definition: translation.definition,
status: translation.status,
})
)}
index={index + 1}
languages={languages}
langui={langui}
/>
))}
)
)}
</div>
)}
</ContentPanel>
@ -147,14 +155,13 @@ export async function getStaticPaths(
const sdk = getReadySdk();
const contents = await sdk.getWikiPagesSlugs();
const paths: GetStaticPathsResult["paths"] = [];
contents.wikiPages?.data.map((wikiPage) => {
context.locales?.map((local) => {
if (wikiPage.attributes)
filterHasAttributes(contents.wikiPages?.data).map((wikiPage) => {
context.locales?.map((local) =>
paths.push({
params: { slug: wikiPage.attributes.slug },
locale: local,
});
});
})
);
});
return {
paths,

View File

@ -12,7 +12,7 @@ import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { prettySlug } from "helpers/formatters";
import { isDefined } from "helpers/others";
import { filterHasAttributes, isDefined } from "helpers/others";
import { GetStaticPropsContext } from "next";
import { Fragment } from "react";
@ -70,9 +70,8 @@ export default function Chronology(props: Props): JSX.Element {
horizontalLine
/>
{chronologyEras.map((era) => (
{filterHasAttributes(chronologyEras).map((era) => (
<Fragment key={era.id}>
{era.attributes && (
<NavOption
url={`#${era.attributes.slug}`}
title={
@ -85,7 +84,6 @@ export default function Chronology(props: Props): JSX.Element {
subtitle={`${era.attributes.starting_year}${era.attributes.ending_year}`}
border
/>
)}
</Fragment>
))}
</SubPanel>

View File

@ -20,6 +20,8 @@ import { Switch } from "components/Inputs/Switch";
import { TextInput } from "components/Inputs/TextInput";
import { WithLabel } from "components/Inputs/WithLabel";
import { useMediaHoverable } from "hooks/useMediaQuery";
import { filterHasAttributes } from "helpers/others";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
interface Props extends AppStaticProps {
pages: NonNullable<GetWikiPagesPreviewsQuery["wikiPages"]>["data"];
@ -93,9 +95,17 @@ export default function Wiki(props: Props): JSX.Element {
className="grid grid-cols-2 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4"
>
{filteredPages.map((page) => (
{/* TODO: Add to langui */}
{filteredPages.length === 0 && (
<ContentPlaceholder
message={
"No results. You can try changing or resetting the search parameters."
}
icon={Icon.ChevronLeft}
/>
)}
{filterHasAttributes(filteredPages).map((page) => (
<Fragment key={page.id}>
{page.attributes && (
<TranslatedPreviewCard
href={`/wiki/${page.attributes.slug}`}
translations={page.attributes.translations?.map(
@ -116,7 +126,6 @@ export default function Wiki(props: Props): JSX.Element {
(category) => category.attributes?.short ?? ""
)}
/>
)}
</Fragment>
))}
</div>
@ -150,17 +159,15 @@ export async function getStaticProps(
}
function sortPages(pages: Props["pages"]): Props["pages"] {
const sortedPages = [...pages];
sortedPages.sort((a, b) => {
return pages.sort((a, b) => {
const slugA = a.attributes?.slug ?? "";
const slugB = b.attributes?.slug ?? "";
return slugA.localeCompare(slugB);
});
return sortedPages;
}
function filterPages(posts: Props["pages"], searchName: string) {
return [...posts].filter((post) => {
return posts.filter((post) => {
if (searchName.length > 1) {
if (
post.attributes?.translations?.[0]?.title

View File

@ -31,7 +31,7 @@ mark {
/* SCROLLBARS STYLING */
* {
@apply [scrollbar-color:theme(colors.dark)_transparent] [scrollbar-width:thin];
@apply [scrollbar-color:theme(colors.dark/1)_transparent] [scrollbar-width:thin];
}
*::-webkit-scrollbar {