Improved the filterHasAttributes + Chip

This commit is contained in:
DrMint 2022-07-10 02:47:32 +02:00
parent ae25df8d72
commit de3f385458
31 changed files with 828 additions and 742 deletions

View File

@ -188,14 +188,13 @@ export const AppLayout = ({
return memo; return memo;
}, [router.locale, router.locales]); }, [router.locale, router.locales]);
const currencyOptions = useMemo(() => { const currencyOptions = useMemo(
const memo: string[] = []; () =>
filterHasAttributes(currencies).map((currentCurrency) => { filterHasAttributes(currencies, ["attributes"] as const).map(
if (isDefinedAndNotEmpty(currentCurrency.attributes.code)) (currentCurrency) => currentCurrency.attributes.code
memo.push(currentCurrency.attributes.code); ),
}); [currencies]
return memo; );
}, [currencies]);
const [currencySelect, setCurrencySelect] = useState<number>(-1); const [currencySelect, setCurrencySelect] = useState<number>(-1);

View File

@ -7,12 +7,12 @@ import { cJoin } from "helpers/className";
interface Props { interface Props {
className?: string; className?: string;
children: React.ReactNode; text: string;
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export const Chip = ({ className, children }: Props): JSX.Element => ( export const Chip = ({ className, text }: Props): JSX.Element => (
<div <div
className={cJoin( className={cJoin(
`grid place-content-center place-items-center whitespace-nowrap rounded-full `grid place-content-center place-items-center whitespace-nowrap rounded-full
@ -21,6 +21,6 @@ export const Chip = ({ className, children }: Props): JSX.Element => (
className className
)} )}
> >
{children} {text}
</div> </div>
); );

View File

@ -103,7 +103,7 @@ export const ScanSet = ({
}); });
const pages = useMemo( const pages = useMemo(
() => selectedScan && filterHasAttributes(selectedScan.pages?.data), () => filterHasAttributes(selectedScan?.pages?.data, ["attributes"]),
[selectedScan] [selectedScan]
); );
@ -119,12 +119,15 @@ export const ScanSet = ({
{title} {title}
</h2> </h2>
<Chip> {/* TODO: Add Scan and Scanlation to langui */}
{selectedScan.language?.data?.attributes?.code === <Chip
text={
selectedScan.language?.data?.attributes?.code ===
selectedScan.source_language?.data?.attributes?.code selectedScan.source_language?.data?.attributes?.code
? "Scan" ? "Scan"
: "Scanlation"} : "Scanlation"
</Chip> }
/>
</div> </div>
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
@ -144,7 +147,7 @@ export const ScanSet = ({
content={getStatusDescription(selectedScan.status, langui)} content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"} maxWidth={"20rem"}
> >
<Chip>{selectedScan.status}</Chip> <Chip text={selectedScan.status} />
</ToolTip> </ToolTip>
</div> </div>
@ -153,16 +156,17 @@ export const ScanSet = ({
{/* TODO: Add Scanner to langui */} {/* TODO: Add Scanner to langui */}
<p className="font-headers font-bold">{"Scanners"}:</p> <p className="font-headers font-bold">{"Scanners"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data).map( {filterHasAttributes(selectedScan.scanners.data, [
(scanner) => ( "id",
"attributes",
] as const).map((scanner) => (
<Fragment key={scanner.id}> <Fragment key={scanner.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={scanner.attributes} recorder={scanner.attributes}
/> />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -172,16 +176,17 @@ export const ScanSet = ({
{/* TODO: Add Cleaners to langui */} {/* TODO: Add Cleaners to langui */}
<p className="font-headers font-bold">{"Cleaners"}:</p> <p className="font-headers font-bold">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data).map( {filterHasAttributes(selectedScan.cleaners.data, [
(cleaner) => ( "id",
"attributes",
] as const).map((cleaner) => (
<Fragment key={cleaner.id}> <Fragment key={cleaner.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={cleaner.attributes} recorder={cleaner.attributes}
/> />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -189,27 +194,28 @@ export const ScanSet = ({
{selectedScan.typesetters && {selectedScan.typesetters &&
selectedScan.typesetters.data.length > 0 && ( selectedScan.typesetters.data.length > 0 && (
<div> <div>
{/* TODO: Add Cleaners to Typesetters */} {/* TODO: Add typesetter to langui */}
<p className="font-headers font-bold">{"Typesetters"}:</p> <p className="font-headers font-bold">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data).map( {filterHasAttributes(selectedScan.typesetters.data, [
(typesetter) => ( "id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}> <Fragment key={typesetter.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={typesetter.attributes} recorder={typesetter.attributes}
/> />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
{isDefinedAndNotEmpty(selectedScan.notes) && ( {isDefinedAndNotEmpty(selectedScan.notes) && (
<ToolTip content={selectedScan.notes}> <ToolTip content={selectedScan.notes}>
{/* TODO: Add Notes to Typesetters */} {/* TODO: Add Notes to langui */}
<Chip>{"Notes"}</Chip> <Chip text={"Notes"} />
</ToolTip> </ToolTip>
)} )}
</div> </div>
@ -224,13 +230,9 @@ export const ScanSet = ({
className="cursor-pointer transition-transform className="cursor-pointer transition-transform
drop-shadow-shade-lg hover:scale-[1.02]" drop-shadow-shade-lg hover:scale-[1.02]"
onClick={() => { onClick={() => {
const images: string[] = []; const images = pages.map((image) =>
pages.map((image) => {
if (isDefinedAndNotEmpty(image.attributes.url))
images.push(
getAssetURL(image.attributes.url, ImageQuality.Large) getAssetURL(image.attributes.url, ImageQuality.Large)
); );
});
openLightBox(images, index); openLightBox(images, index);
}} }}
> >

View File

@ -80,12 +80,15 @@ export const ScanSetCover = ({
{"Cover"} {"Cover"}
</h2> </h2>
<Chip> {/* TODO: Add Scan and Scanlation to langui */}
{selectedScan.language?.data?.attributes?.code === <Chip
text={
selectedScan.language?.data?.attributes?.code ===
selectedScan.source_language?.data?.attributes?.code selectedScan.source_language?.data?.attributes?.code
? "Scan" ? "Scan"
: "Scanlation"} : "Scanlation"
</Chip> }
/>
</div> </div>
<div className="flex flex-row flex-wrap place-items-center gap-4 pb-6"> <div className="flex flex-row flex-wrap place-items-center gap-4 pb-6">
@ -97,7 +100,7 @@ export const ScanSetCover = ({
content={getStatusDescription(selectedScan.status, langui)} content={getStatusDescription(selectedScan.status, langui)}
maxWidth={"20rem"} maxWidth={"20rem"}
> >
<Chip>{selectedScan.status}</Chip> <Chip text={selectedScan.status} />
</ToolTip> </ToolTip>
</div> </div>
@ -106,16 +109,17 @@ export const ScanSetCover = ({
{/* TODO: Add Scanner to langui */} {/* TODO: Add Scanner to langui */}
<p className="font-headers font-bold">{"Scanners"}:</p> <p className="font-headers font-bold">{"Scanners"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.scanners.data).map( {filterHasAttributes(selectedScan.scanners.data, [
(scanner) => ( "id",
"attributes",
] as const).map((scanner) => (
<Fragment key={scanner.id}> <Fragment key={scanner.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={scanner.attributes} recorder={scanner.attributes}
/> />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -125,16 +129,17 @@ export const ScanSetCover = ({
{/* TODO: Add Cleaners to langui */} {/* TODO: Add Cleaners to langui */}
<p className="font-headers font-bold">{"Cleaners"}:</p> <p className="font-headers font-bold">{"Cleaners"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.cleaners.data).map( {filterHasAttributes(selectedScan.cleaners.data, [
(cleaner) => ( "id",
"attributes",
] as const).map((cleaner) => (
<Fragment key={cleaner.id}> <Fragment key={cleaner.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={cleaner.attributes} recorder={cleaner.attributes}
/> />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -145,16 +150,17 @@ export const ScanSetCover = ({
{/* TODO: Add Cleaners to Typesetters */} {/* TODO: Add Cleaners to Typesetters */}
<p className="font-headers font-bold">{"Typesetters"}:</p> <p className="font-headers font-bold">{"Typesetters"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(selectedScan.typesetters.data).map( {filterHasAttributes(selectedScan.typesetters.data, [
(typesetter) => ( "id",
"attributes",
] as const).map((typesetter) => (
<Fragment key={typesetter.id}> <Fragment key={typesetter.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}
recorder={typesetter.attributes} recorder={typesetter.attributes}
/> />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -171,11 +177,10 @@ export const ScanSetCover = ({
className="cursor-pointer transition-transform className="cursor-pointer transition-transform
drop-shadow-shade-lg hover:scale-[1.02]" drop-shadow-shade-lg hover:scale-[1.02]"
onClick={() => { onClick={() => {
const imgs: string[] = []; const imgs = coverImages.map((img) =>
coverImages.map((img) => { getAssetURL(img.url, ImageQuality.Large)
if (img.url) );
imgs.push(getAssetURL(img.url, ImageQuality.Large));
});
openLightBox(imgs, index); openLightBox(imgs, index);
}} }}
> >

View File

@ -104,7 +104,7 @@ export const PostPage = ({
)} )}
maxWidth={"20rem"} maxWidth={"20rem"}
> >
<Chip>{selectedTranslation.status}</Chip> <Chip text={selectedTranslation.status} />
</ToolTip> </ToolTip>
</div> </div>
)} )}
@ -113,7 +113,10 @@ export const PostPage = ({
<div> <div>
<p className="font-headers font-bold">{"Authors"}:</p> <p className="font-headers font-bold">{"Authors"}:</p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(post.authors.data).map((author) => ( {filterHasAttributes(post.authors.data, [
"id",
"attributes",
] as const).map((author) => (
<Fragment key={author.id}> <Fragment key={author.id}>
<RecorderChip <RecorderChip
langui={langui} langui={langui}

View File

@ -262,7 +262,7 @@ export const PreviewCard = ({
{topChips && topChips.length > 0 && ( {topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => ( {topChips.map((text, index) => (
<Chip key={index}>{text}</Chip> <Chip key={index} text={text} />
))} ))}
</div> </div>
)} )}
@ -281,9 +281,7 @@ export const PreviewCard = ({
{bottomChips && bottomChips.length > 0 && ( {bottomChips && bottomChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{bottomChips.map((text, index) => ( {bottomChips.map((text, index) => (
<Chip key={index} className="text-sm"> <Chip key={index} className="text-sm" text={text} />
{text}
</Chip>
))} ))}
</div> </div>
)} )}

View File

@ -52,7 +52,7 @@ const PreviewLine = ({
{topChips && topChips.length > 0 && ( {topChips && topChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{topChips.map((text, index) => ( {topChips.map((text, index) => (
<Chip key={index}>{text}</Chip> <Chip key={index} text={text} />
))} ))}
</div> </div>
)} )}
@ -68,9 +68,7 @@ const PreviewLine = ({
{bottomChips && bottomChips.length > 0 && ( {bottomChips && bottomChips.length > 0 && (
<div className="grid grid-flow-col place-content-start gap-1 overflow-hidden"> <div className="grid grid-flow-col place-content-start gap-1 overflow-hidden">
{bottomChips.map((text, index) => ( {bottomChips.map((text, index) => (
<Chip key={index} className="text-sm"> <Chip key={index} className="text-sm" text={text} />
{text}
</Chip>
))} ))}
</div> </div>
)} )}

View File

@ -38,19 +38,19 @@ export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => (
{recorder.languages?.data && recorder.languages.data.length > 0 && ( {recorder.languages?.data && recorder.languages.data.length > 0 && (
<div className="flex flex-row flex-wrap gap-1"> <div className="flex flex-row flex-wrap gap-1">
<p>{langui.languages}:</p> <p>{langui.languages}:</p>
{filterHasAttributes(recorder.languages.data).map( {filterHasAttributes(recorder.languages.data, [
(language) => ( "attributes",
] as const).map((language) => (
<Fragment key={language.attributes.code}> <Fragment key={language.attributes.code}>
<Chip>{language.attributes.code.toUpperCase()}</Chip> <Chip text={language.attributes.code.toUpperCase()} />
</Fragment> </Fragment>
) ))}
)}
</div> </div>
)} )}
{recorder.pronouns && ( {recorder.pronouns && (
<div className="flex flex-row flex-wrap gap-1"> <div className="flex flex-row flex-wrap gap-1">
<p>{langui.pronouns}:</p> <p>{langui.pronouns}:</p>
<Chip>{recorder.pronouns}</Chip> <Chip text={recorder.pronouns} />
</div> </div>
)} )}
</div> </div>
@ -60,10 +60,13 @@ export const RecorderChip = ({ recorder, langui }: Props): JSX.Element => (
} }
placement="top" placement="top"
> >
<Chip key={recorder.anonymous_code}> <Chip
{recorder.anonymize key={recorder.anonymous_code}
text={
recorder.anonymize
? `Recorder#${recorder.anonymous_code}` ? `Recorder#${recorder.anonymous_code}`
: recorder.username} : recorder.username
</Chip> }
/>
</ToolTip> </ToolTip>
); );

View File

@ -146,11 +146,13 @@ export const SmartList = <T,>({
first-of-type:pt-0" first-of-type:pt-0"
> >
{name} {name}
<Chip>{`${groupItems.length} ${ <Chip
text={`${groupItems.length} ${
groupItems.length <= 1 groupItems.length <= 1
? langui.result?.toLowerCase() ?? "" ? langui.result?.toLowerCase() ?? ""
: langui.results?.toLowerCase() ?? "" : langui.results?.toLowerCase() ?? ""
}`}</Chip> }`}
/>
</h2> </h2>
)} )}
<div <div

View File

@ -80,12 +80,12 @@ export const ThumbnailHeader = ({
<div className="flex flex-col place-items-center gap-2"> <div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.type}</h3> <h3 className="text-xl">{langui.type}</h3>
<div className="flex flex-row flex-wrap"> <div className="flex flex-row flex-wrap">
<Chip> <Chip
{type.data.attributes.titles && text={
type.data.attributes.titles.length > 0 type.data.attributes.titles?.[0]?.title ??
? type.data.attributes.titles[0]?.title prettySlug(type.data.attributes.slug)
: prettySlug(type.data.attributes.slug)} }
</Chip> />
</div> </div>
</div> </div>
)} )}
@ -94,8 +94,11 @@ export const ThumbnailHeader = ({
<div className="flex flex-col place-items-center gap-2"> <div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3> <h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{filterHasAttributes(categories.data).map((category) => ( {filterHasAttributes(categories.data, [
<Chip key={category.id}>{category.attributes.name}</Chip> "attributes",
"id",
] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} />
))} ))}
</div> </div>
</div> </div>

View File

@ -57,7 +57,7 @@ export const ChronologyItemComponent = ({
filterHasAttributes(item.attributes.events, [ filterHasAttributes(item.attributes.events, [
"id", "id",
"translations", "translations",
]).map((event) => ( ] as const).map((event) => (
<Fragment key={event.id}> <Fragment key={event.id}>
<div className="m-0"> <div className="m-0">
{filterDefined(event.translations).map( {filterDefined(event.translations).map(
@ -77,7 +77,7 @@ export const ChronologyItemComponent = ({
)} )}
maxWidth={"20rem"} maxWidth={"20rem"}
> >
<Chip>{translation.status}</Chip> <Chip text={translation.status} />
</ToolTip> </ToolTip>
)} )}
{translation.title ? ( {translation.title ? (

View File

@ -62,7 +62,7 @@ const DefinitionCard = ({
content={getStatusDescription(selectedTranslation.status, langui)} content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"} maxWidth={"20rem"}
> >
<Chip>{selectedTranslation.status}</Chip> <Chip text={selectedTranslation.status} />
</ToolTip> </ToolTip>
</> </>
)} )}
@ -72,7 +72,7 @@ const DefinitionCard = ({
<Separator /> <Separator />
<div className="flex flex-row gap-1"> <div className="flex flex-row gap-1">
{categories.map((category, categoryIndex) => ( {categories.map((category, categoryIndex) => (
<Chip key={categoryIndex}>{category}</Chip> <Chip key={categoryIndex} text={category} />
))} ))}
</div> </div>
</> </>

View File

@ -60,20 +60,20 @@ export const prettyinlineTitle = (
export const prettyItemType = ( export const prettyItemType = (
metadata: any, metadata: any,
langui: AppStaticProps["langui"] langui: AppStaticProps["langui"]
): string | null | undefined => { ): string => {
switch (metadata.__typename) { switch (metadata.__typename) {
case "ComponentMetadataAudio": case "ComponentMetadataAudio":
return langui.audio; return langui.audio ?? "Audio";
case "ComponentMetadataBooks": case "ComponentMetadataBooks":
return langui.textual; return langui.textual ?? "Textual";
case "ComponentMetadataGame": case "ComponentMetadataGame":
return langui.game; return langui.game ?? "Game";
case "ComponentMetadataVideo": case "ComponentMetadataVideo":
return langui.video; return langui.video ?? "Video";
case "ComponentMetadataGroup": case "ComponentMetadataGroup":
return langui.group; return langui.group ?? "Group";
case "ComponentMetadataOther": case "ComponentMetadataOther":
return langui.other; return langui.other ?? "Other";
default: default:
return ""; return "";
} }

View File

@ -1,5 +1,5 @@
import { AppStaticProps } from "../graphql/getAppStaticProps"; import { AppStaticProps } from "../graphql/getAppStaticProps";
import { SelectiveRequiredNonNullable } from "./types"; import { PathDot, SelectiveNonNullable } from "./types/SelectiveNonNullable";
import { import {
Enum_Componentsetstextset_Status, Enum_Componentsetstextset_Status,
GetLibraryItemQuery, GetLibraryItemQuery,
@ -71,21 +71,29 @@ export const filterDefined = <T>(t: T[] | null | undefined): NonNullable<T>[] =>
? [] ? []
: (t.filter((item) => isDefined(item)) as NonNullable<T>[]); : (t.filter((item) => isDefined(item)) as NonNullable<T>[]);
export const filterHasAttributes = <T, P extends keyof NonNullable<T>>( export const filterHasAttributes = <T, P extends PathDot<T>>(
t: T[] | null | undefined, t: T[] | null | undefined,
attributes?: P[] paths: readonly P[]
): SelectiveRequiredNonNullable<NonNullable<T>, P>[] => ): SelectiveNonNullable<T, typeof paths[number]>[] =>
isUndefined(t) isUndefined(t)
? [] ? []
: (t.filter((item) => { : (t.filter((item) =>
hasAttributes(item, paths)
) as unknown as SelectiveNonNullable<T, typeof paths[number]>[]);
const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean => {
if (isDefined(item)) { if (isDefined(item)) {
const attributesToCheck = attributes ?? (Object.keys(item) as P[]); return paths.every((path) => {
return attributesToCheck.every((attribute) => const attributeToCheck = (path as string).split(".")[0];
isDefined(item[attribute]) return (
isDefined(attributeToCheck) &&
Object.keys(item).includes(attributeToCheck) &&
isDefined(item[attributeToCheck as keyof T])
); );
});
} }
return false; return false;
}) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]); };
export const iterateMap = <K, V, U>( export const iterateMap = <K, V, U>(
map: Map<K, V>, map: Map<K, V>,
@ -95,7 +103,6 @@ export const iterateMap = <K, V, U>(
const toList = [...map]; const toList = [...map];
if (isDefined(sortingFunction)) { if (isDefined(sortingFunction)) {
toList.sort(sortingFunction); toList.sort(sortingFunction);
console.log(toList.sort(sortingFunction));
} }
return toList.map(([key, value], index) => callbackfn(key, value, index)); return toList.map(([key, value], index) => callbackfn(key, value, index));
}; };

View File

@ -4,6 +4,8 @@ import {
GetWikiPageQuery, GetWikiPageQuery,
} from "graphql/generated"; } from "graphql/generated";
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
type Post = NonNullable< type Post = NonNullable<
NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"] NonNullable<GetPostQuery["posts"]>["data"][number]["attributes"]
>; >;
@ -12,6 +14,8 @@ export interface PostWithTranslations extends Omit<Post, "translations"> {
translations: NonNullable<Post["translations"]>; translations: NonNullable<Post["translations"]>;
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export type Content = NonNullable< export type Content = NonNullable<
NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"] NonNullable<GetContentTextQuery["contents"]>["data"][number]["attributes"]
>; >;
@ -20,6 +24,8 @@ export interface ContentWithTranslations extends Omit<Content, "translations"> {
translations: NonNullable<Content["translations"]>; translations: NonNullable<Content["translations"]>;
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
type WikiPage = NonNullable< type WikiPage = NonNullable<
NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number]["attributes"] NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number]["attributes"]
>; >;
@ -29,13 +35,13 @@ export interface WikiPageWithTranslations
translations: NonNullable<WikiPage["translations"]>; translations: NonNullable<WikiPage["translations"]>;
} }
// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
export type RequiredNonNullable<T> = { export type RequiredNonNullable<T> = {
[P in keyof T]-?: NonNullable<T[P]>; [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 { export enum LibraryItemUserStatus {
None = 0, None = 0,

View File

@ -0,0 +1,47 @@
type JoinDot<K extends string, P extends string> = `${K}${"" extends K
? ""
: "."}${P}`;
export type PathDot<T, Acc extends string = ""> = T extends object
? {
[K in keyof T]: K extends string
? JoinDot<Acc, K> | PathDot<T[K], JoinDot<Acc, K>>
: never;
}[keyof T]
: Acc;
type PathHead<T extends unknown[]> = T extends [infer head]
? head
: T extends [infer head, ...infer rest]
? head
: "";
type PathRest<T extends unknown[]> = T extends [infer head, ...infer rest]
? rest extends []
? never
: rest
: never;
type PathLast<T extends unknown[]> = T["length"] extends 1 ? true : false;
type Recursive<T, Path extends unknown[]> = PathHead<Path> extends keyof T
? Omit<T, PathHead<Path>> & {
[P in PathHead<Path>]-?: PathLast<Path> extends true
? NonNullable<T[P]>
: Recursive<NonNullable<T[P]>, PathRest<Path>>;
}
: T;
type Split<
Str,
Cache extends string[] = []
> = Str extends `${infer Method}.${infer PathRest}`
? Split<PathRest, [...Cache, Method]>
: Str extends `${infer PathLast}`
? [...Cache, PathLast]
: never;
export type SelectiveNonNullable<T, P extends PathDot<T>> = Recursive<
NonNullable<T>,
Split<P>
>;

View File

@ -78,7 +78,9 @@ const Channel = ({ langui, channel, ...otherProps }: Props): JSX.Element => {
className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0 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" desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2"
> >
{filterHasAttributes(channel?.videos?.data).map((video) => ( {filterHasAttributes(channel?.videos?.data, [
"attributes",
] as const).map((video) => (
<Fragment key={video.id}> <Fragment key={video.id}>
<PreviewCard <PreviewCard
href={`/archives/videos/v/${video.attributes.uid}`} href={`/archives/videos/v/${video.attributes.uid}`}
@ -152,7 +154,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const channels = await sdk.getVideoChannelsSlugs(); const channels = await sdk.getVideoChannelsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
if (channels.videoChannels?.data) if (channels.videoChannels?.data)
filterHasAttributes(channels.videoChannels.data).map((channel) => { filterHasAttributes(channels.videoChannels.data, [
"attributes",
] as const).map((channel) => {
context.locales?.map((local) => { context.locales?.map((local) => {
paths.push({ paths.push({
params: { uid: channel.attributes.uid }, params: { uid: channel.attributes.uid },

View File

@ -92,7 +92,7 @@ const Videos = ({ langui, videos, ...otherProps }: Props): JSX.Element => {
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<SmartList <SmartList
items={filterHasAttributes(videos)} items={filterHasAttributes(videos, ["id", "attributes"] as const)}
getItemId={(item) => item.id} getItemId={(item) => item.id}
renderItem={({ item }) => ( renderItem={({ item }) => (
<> <>

View File

@ -238,11 +238,13 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const videos = await sdk.getVideosSlugs(); const videos = await sdk.getVideosSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
if (videos.videos?.data) if (videos.videos?.data)
filterHasAttributes(videos.videos.data).map((video) => { filterHasAttributes(videos.videos.data, ["attributes"] as const).map(
(video) => {
context.locales?.map((local) => { context.locales?.map((local) => {
paths.push({ params: { uid: video.attributes.uid }, locale: local }); paths.push({ params: { uid: video.attributes.uid }, locale: local });
}); });
}); }
);
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -114,13 +114,13 @@ const Content = ({
<p className="font-headers font-bold"> <p className="font-headers font-bold">
{langui.source_language}: {langui.source_language}:
</p> </p>
<Chip> <Chip
{prettyLanguage( text={prettyLanguage(
selectedTranslation.text_set.source_language.data.attributes selectedTranslation.text_set.source_language.data.attributes
.code, .code,
languages languages
)} )}
</Chip> />
</div> </div>
)} )}
@ -134,7 +134,7 @@ const Content = ({
)} )}
maxWidth={"20rem"} maxWidth={"20rem"}
> >
<Chip>{selectedTranslation.text_set.status}</Chip> <Chip text={selectedTranslation.text_set.status} />
</ToolTip> </ToolTip>
</div> </div>
@ -146,7 +146,8 @@ const Content = ({
</p> </p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes( {filterHasAttributes(
selectedTranslation.text_set.transcribers.data selectedTranslation.text_set.transcribers.data,
["attributes", "id"] as const
).map((recorder) => ( ).map((recorder) => (
<Fragment key={recorder.id}> <Fragment key={recorder.id}>
<RecorderChip <RecorderChip
@ -167,7 +168,8 @@ const Content = ({
</p> </p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes( {filterHasAttributes(
selectedTranslation.text_set.translators.data selectedTranslation.text_set.translators.data,
["attributes", "id"] as const
).map((recorder) => ( ).map((recorder) => (
<Fragment key={recorder.id}> <Fragment key={recorder.id}>
<RecorderChip <RecorderChip
@ -188,7 +190,8 @@ const Content = ({
</p> </p>
<div className="grid place-content-center place-items-center gap-2"> <div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes( {filterHasAttributes(
selectedTranslation.text_set.proofreaders.data selectedTranslation.text_set.proofreaders.data,
["attributes", "id"] as const
).map((recorder) => ( ).map((recorder) => (
<Fragment key={recorder.id}> <Fragment key={recorder.id}>
<RecorderChip <RecorderChip
@ -221,10 +224,12 @@ const Content = ({
{langui.source} {langui.source}
</p> </p>
<div className="mt-6 grid place-items-center gap-6 text-left"> <div className="mt-6 grid place-items-center gap-6 text-left">
{content.ranged_contents.data.map((rangedContent) => { {filterHasAttributes(content.ranged_contents.data, [
"attributes.library_item.data.attributes",
"attributes.library_item.data.id",
] as const).map((rangedContent) => {
const libraryItem = const libraryItem =
rangedContent.attributes?.library_item?.data; rangedContent.attributes.library_item.data;
if (libraryItem?.attributes && libraryItem.id) {
return ( return (
<div <div
key={libraryItem.attributes.slug} key={libraryItem.attributes.slug}
@ -250,9 +255,10 @@ const Content = ({
] ]
: [] : []
} }
bottomChips={libraryItem.attributes.categories?.data.map( bottomChips={filterHasAttributes(
(category) => category.attributes?.short ?? "" libraryItem.attributes.categories?.data,
)} ["attributes"] as const
).map((category) => category.attributes.short)}
metadata={{ metadata={{
currencies: currencies, currencies: currencies,
release_date: libraryItem.attributes.release_date, release_date: libraryItem.attributes.release_date,
@ -272,8 +278,6 @@ const Content = ({
/> />
</div> </div>
); );
}
return <></>;
})} })}
</div> </div>
</div> </div>
@ -507,14 +511,16 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs(); const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.contents?.data).map((item) => { filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
(item) => {
context.locales?.map((local) => { context.locales?.map((local) => {
paths.push({ paths.push({
params: { slug: item.attributes.slug }, params: { slug: item.attributes.slug },
locale: local, locale: local,
}); });
}); });
}); }
);
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -21,8 +21,8 @@ import { Icon } from "components/Ico";
import { filterDefined, filterHasAttributes } from "helpers/others"; import { filterDefined, filterHasAttributes } from "helpers/others";
import { GetContentsQuery } from "graphql/generated"; import { GetContentsQuery } from "graphql/generated";
import { SmartList } from "components/SmartList"; import { SmartList } from "components/SmartList";
import { SelectiveRequiredNonNullable } from "helpers/types";
import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
/* /*
* *
@ -73,7 +73,7 @@ const Contents = ({
const groupingFunction = useCallback( const groupingFunction = useCallback(
( (
item: SelectiveRequiredNonNullable< item: SelectiveNonNullable<
NonNullable<GetContentsQuery["contents"]>["data"][number], NonNullable<GetContentsQuery["contents"]>["data"][number],
"attributes" | "id" "attributes" | "id"
> >
@ -81,7 +81,8 @@ const Contents = ({
switch (groupingMethod) { switch (groupingMethod) {
case 0: { case 0: {
const categories = filterHasAttributes( const categories = filterHasAttributes(
item.attributes.categories?.data item.attributes.categories?.data,
["attributes"] as const
); );
if (categories.length > 0) { if (categories.length > 0) {
return categories.map((category) => category.attributes.name); return categories.map((category) => category.attributes.name);
@ -106,10 +107,7 @@ const Contents = ({
const filteringFunction = useCallback( const filteringFunction = useCallback(
( (
item: SelectiveRequiredNonNullable< item: SelectiveNonNullable<Props["contents"][number], "attributes" | "id">
Props["contents"][number],
"attributes" | "id"
>
) => { ) => {
if ( if (
effectiveCombineRelatedContent && effectiveCombineRelatedContent &&
@ -217,7 +215,7 @@ const Contents = ({
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<SmartList <SmartList
items={filterHasAttributes(contents)} items={filterHasAttributes(contents, ["attributes", "id"] as const)}
getItemId={(item) => item.id} getItemId={(item) => item.id}
renderItem={({ item }) => ( renderItem={({ item }) => (
<> <>

View File

@ -60,7 +60,7 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
/> />
<p>{line.subitems.join(" -> ")}</p> <p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p> <p>{line.name}</p>
<Chip>{line.type}</Chip> <Chip text={line.type} />
<Chip <Chip
className={ className={
line.severity === "Very High" line.severity === "Very High"
@ -71,9 +71,8 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
? "bg-[#fff344] !opacity-100" ? "bg-[#fff344] !opacity-100"
: "" : ""
} }
> text={line.severity}
{line.severity} />
</Chip>
<ToolTip content={line.recommandation} placement="left"> <ToolTip content={line.recommandation} placement="left">
<p>{line.description}</p> <p>{line.description}</p>
</ToolTip> </ToolTip>
@ -138,7 +137,8 @@ const testingContent = (contents: Props["contents"]): Report => {
lines: [], lines: [],
}; };
filterHasAttributes(contents.contents?.data).map((content) => { filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
(content) => {
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`; 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}`; const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
@ -452,6 +452,7 @@ const testingContent = (contents: Props["contents"]): Report => {
} }
); );
} }
}); }
);
return report; return report;
}; };

View File

@ -65,7 +65,7 @@ const CheckupLibraryItems = ({
/> />
<p>{line.subitems.join(" -> ")}</p> <p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p> <p>{line.name}</p>
<Chip>{line.type}</Chip> <Chip text={line.type} />
<Chip <Chip
className={ className={
line.severity === "Very High" line.severity === "Very High"
@ -76,9 +76,8 @@ const CheckupLibraryItems = ({
? "bg-[#fff344] !opacity-100" ? "bg-[#fff344] !opacity-100"
: "" : ""
} }
> text={line.severity}
{line.severity} />
</Chip>
<ToolTip content={line.recommandation} placement="left"> <ToolTip content={line.recommandation} placement="left">
<p>{line.description}</p> <p>{line.description}</p>
</ToolTip> </ToolTip>

View File

@ -221,7 +221,8 @@ const LibrarySlug = ({
{item.urls?.length ? ( {item.urls?.length ? (
<div className="flex flex-row place-items-center gap-3"> <div className="flex flex-row place-items-center gap-3">
<p>{langui.available_at}</p> <p>{langui.available_at}</p>
{filterHasAttributes(item.urls).map((url, index) => ( {filterHasAttributes(item.urls, ["url"] as const).map(
(url, index) => (
<Fragment key={index}> <Fragment key={index}>
<Button <Button
href={url.url} href={url.url}
@ -229,7 +230,8 @@ const LibrarySlug = ({
text={prettyURL(url.url)} text={prettyURL(url.url)}
/> />
</Fragment> </Fragment>
))} )
)}
</div> </div>
) : ( ) : (
<p>{langui.item_not_available}</p> <p>{langui.item_not_available}</p>
@ -246,20 +248,20 @@ const LibrarySlug = ({
className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end
gap-8" gap-8"
> >
{filterHasAttributes(item.gallery.data).map( {filterHasAttributes(item.gallery.data, [
(galleryItem, index) => ( "id",
"attributes",
] as const).map((galleryItem, index) => (
<Fragment key={galleryItem.id}> <Fragment key={galleryItem.id}>
<div <div
className="relative aspect-square cursor-pointer className="relative aspect-square cursor-pointer
transition-transform hover:scale-[1.02]" transition-transform hover:scale-[1.02]"
onClick={() => { onClick={() => {
const images: string[] = filterHasAttributes( const images: string[] = filterHasAttributes(
item.gallery?.data item.gallery?.data,
["attributes"] as const
).map((image) => ).map((image) =>
getAssetURL( getAssetURL(image.attributes.url, ImageQuality.Large)
image.attributes.url,
ImageQuality.Large
)
); );
openLightBox(images, index); openLightBox(images, index);
}} }}
@ -271,8 +273,7 @@ const LibrarySlug = ({
/> />
</div> </div>
</Fragment> </Fragment>
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -288,9 +289,9 @@ const LibrarySlug = ({
<div className="grid place-content-start place-items-center"> <div className="grid place-content-start place-items-center">
<h3 className="text-xl">{langui.type}</h3> <h3 className="text-xl">{langui.type}</h3>
<div className="grid grid-flow-col gap-1"> <div className="grid grid-flow-col gap-1">
<Chip>{prettyItemType(item.metadata[0], langui)}</Chip> <Chip text={prettyItemType(item.metadata[0], langui)} />
{""} {""}
<Chip>{prettyItemSubType(item.metadata[0])}</Chip> <Chip text={prettyItemSubType(item.metadata[0])} />
</div> </div>
</div> </div>
)} )}
@ -331,8 +332,10 @@ const LibrarySlug = ({
<div className="flex flex-col place-items-center gap-2"> <div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3> <h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{item.categories.data.map((category) => ( {filterHasAttributes(item.categories.data, [
<Chip key={category.id}>{category.attributes?.name}</Chip> "attributes",
] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} />
))} ))}
</div> </div>
</div> </div>
@ -458,7 +461,10 @@ const LibrarySlug = ({
className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]
items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1" items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1"
> >
{filterHasAttributes(item.subitems.data).map((subitem) => ( {filterHasAttributes(item.subitems.data, [
"id",
"attributes",
] as const).map((subitem) => (
<Fragment key={subitem.id}> <Fragment key={subitem.id}>
<PreviewCard <PreviewCard
href={`/library/${subitem.attributes.slug}`} href={`/library/${subitem.attributes.slug}`}
@ -506,8 +512,9 @@ const LibrarySlug = ({
/> />
)} )}
<div className="grid w-full gap-4"> <div className="grid w-full gap-4">
{filterHasAttributes(item.contents.data).map( {filterHasAttributes(item.contents.data, [
(rangedContent) => ( "attributes",
] as const).map((rangedContent) => (
<ContentLine <ContentLine
content={ content={
rangedContent.attributes.content?.data?.attributes rangedContent.attributes.content?.data?.attributes
@ -524,14 +531,15 @@ const LibrarySlug = ({
})), })),
categories: filterHasAttributes( categories: filterHasAttributes(
rangedContent.attributes.content.data.attributes rangedContent.attributes.content.data.attributes
.categories?.data .categories?.data,
["attributes"]
).map((category) => category.attributes.short), ).map((category) => category.attributes.short),
type: type:
rangedContent.attributes.content.data.attributes rangedContent.attributes.content.data.attributes
.type?.data?.attributes?.titles?.[0]?.title ?? .type?.data?.attributes?.titles?.[0]?.title ??
prettySlug( prettySlug(
rangedContent.attributes.content.data rangedContent.attributes.content.data.attributes
.attributes.type?.data?.attributes?.slug .type?.data?.attributes?.slug
), ),
slug: rangedContent.attributes.content.data slug: rangedContent.attributes.content.data
.attributes.slug, .attributes.slug,
@ -554,8 +562,7 @@ const LibrarySlug = ({
rangedContent.attributes.scan_set.length > 0 rangedContent.attributes.scan_set.length > 0
} }
/> />
) ))}
)}
</div> </div>
</div> </div>
)} )}
@ -643,7 +650,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs(); const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { filterHasAttributes(libraryItems.libraryItems?.data, [
"attributes",
] as const).map((item) => {
context.locales?.map((local) => context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local }) paths.push({ params: { slug: item.attributes.slug }, locale: local })
); );
@ -702,8 +711,6 @@ const ContentLine = ({
), ),
}); });
console.log(prettySlug(slug, parentSlug));
return ( return (
<div <div
className={cJoin( className={cJoin(
@ -730,13 +737,13 @@ const ContentLine = ({
</a> </a>
<div className="flex flex-row flex-wrap gap-1"> <div className="flex flex-row flex-wrap gap-1">
{content?.categories?.map((category, index) => ( {content?.categories?.map((category, index) => (
<Chip key={index}>{category}</Chip> <Chip key={index} text={category} />
))} ))}
</div> </div>
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p> <p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
<p>{rangeStart}</p> <p>{rangeStart}</p>
{content?.type && ( {content?.type && (
<Chip className="justify-self-end thin:hidden">{content.type}</Chip> <Chip className="justify-self-end thin:hidden" text={content.type} />
)} )}
</div> </div>
<div <div

View File

@ -177,7 +177,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs({}); const libraryItems = await sdk.getLibraryItemsSlugs({});
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { filterHasAttributes(libraryItems.libraryItems?.data, [
"attributes",
] as const).map((item) => {
context.locales?.map((local) => context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local }) paths.push({ params: { slug: item.attributes.slug }, locale: local })
); );

View File

@ -17,10 +17,7 @@ import {
prettyinlineTitle, prettyinlineTitle,
prettyItemSubType, prettyItemSubType,
} from "helpers/formatters"; } from "helpers/formatters";
import { import { LibraryItemUserStatus } from "helpers/types";
LibraryItemUserStatus,
SelectiveRequiredNonNullable,
} from "helpers/types";
import { Icon } from "components/Ico"; import { Icon } from "components/Ico";
import { WithLabel } from "components/Inputs/WithLabel"; import { WithLabel } from "components/Inputs/WithLabel";
import { TextInput } from "components/Inputs/TextInput"; import { TextInput } from "components/Inputs/TextInput";
@ -35,6 +32,7 @@ import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholde
import { useAppLayout } from "contexts/AppLayoutContext"; import { useAppLayout } from "contexts/AppLayoutContext";
import { convertPrice } from "helpers/numbers"; import { convertPrice } from "helpers/numbers";
import { SmartList } from "components/SmartList"; import { SmartList } from "components/SmartList";
import { SelectiveNonNullable } from "helpers/types/SelectiveNonNullable";
/* /*
* *
@ -97,10 +95,7 @@ const Library = ({
const filteringFunction = useCallback( const filteringFunction = useCallback(
( (
item: SelectiveRequiredNonNullable< item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
Props["items"][number],
"attributes" | "id"
>
) => { ) => {
if (!showSubitems && !item.attributes.root_item) return false; if (!showSubitems && !item.attributes.root_item) return false;
if ( if (
@ -143,14 +138,8 @@ const Library = ({
const sortingFunction = useCallback( const sortingFunction = useCallback(
( (
a: SelectiveRequiredNonNullable< a: SelectiveNonNullable<Props["items"][number], "attributes" | "id">,
Props["items"][number], b: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
"attributes" | "id"
>,
b: SelectiveRequiredNonNullable<
Props["items"][number],
"attributes" | "id"
>
) => { ) => {
switch (sortingMethod) { switch (sortingMethod) {
case 0: { case 0: {
@ -193,15 +182,13 @@ const Library = ({
const groupingFunction = useCallback( const groupingFunction = useCallback(
( (
item: SelectiveRequiredNonNullable< item: SelectiveNonNullable<Props["items"][number], "attributes" | "id">
Props["items"][number],
"attributes" | "id"
>
): string[] => { ): string[] => {
switch (groupingMethod) { switch (groupingMethod) {
case 0: { case 0: {
const categories = filterHasAttributes( const categories = filterHasAttributes(
item.attributes.categories?.data item.attributes.categories?.data,
["attributes"] as const
); );
if (categories.length > 0) { if (categories.length > 0) {
return categories.map((category) => category.attributes.name); return categories.map((category) => category.attributes.name);
@ -406,7 +393,7 @@ const Library = ({
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<SmartList <SmartList
items={filterHasAttributes(items)} items={filterHasAttributes(items, ["id", "attributes"] as const)}
getItemId={(item) => item.id} getItemId={(item) => item.id}
renderItem={({ item }) => ( renderItem={({ item }) => (
<PreviewCard <PreviewCard

View File

@ -47,11 +47,13 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const posts = await sdk.getPostsSlugs(); const posts = await sdk.getPostsSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(posts.posts?.data).map((item) => { filterHasAttributes(posts.posts?.data, ["attributes"] as const).map(
(item) => {
context.locales?.map((local) => context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local }) paths.push({ params: { slug: item.attributes.slug }, locale: local })
); );
}); }
);
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -97,7 +97,7 @@ const News = ({
() => ( () => (
<ContentPanel width={ContentPanelWidthSizes.Full}> <ContentPanel width={ContentPanelWidthSizes.Full}>
<SmartList <SmartList
items={filterHasAttributes(posts)} items={filterHasAttributes(posts, ["attributes", "id"] as const)}
getItemId={(post) => post.id} getItemId={(post) => post.id}
langui={langui} langui={langui}
renderItem={({ item: post }) => ( renderItem={({ item: post }) => (

View File

@ -100,8 +100,10 @@ const WikiPage = ({
{langui.categories} {langui.categories}
</p> </p>
<div className="flex flex-row flex-wrap place-content-center gap-2"> <div className="flex flex-row flex-wrap place-content-center gap-2">
{page.categories?.data.map((category) => ( {filterHasAttributes(page.categories?.data, [
<Chip key={category.id}>{category.attributes?.name}</Chip> "attributes",
] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} />
))} ))}
</div> </div>
</div> </div>
@ -116,30 +118,28 @@ const WikiPage = ({
</div> </div>
)} )}
{filterHasAttributes(page.definitions, ["translations"]).map( {filterHasAttributes(page.definitions, [
(definition, index) => ( "translations",
] as const).map((definition, index) => (
<> <>
<DefinitionCard <DefinitionCard
key={index} key={index}
source={definition.source?.data?.attributes?.name} source={definition.source?.data?.attributes?.name}
translations={filterHasAttributes( translations={definition.translations.map((translation) => ({
definition.translations language: translation?.language?.data?.attributes?.code,
).map((translation) => ({ definition: translation?.definition,
language: translation.language.data?.attributes?.code, status: translation?.status,
definition: translation.definition,
status: translation.status,
}))} }))}
index={index + 1} index={index + 1}
languages={languages} languages={languages}
langui={langui} langui={langui}
categories={filterHasAttributes( categories={filterHasAttributes(definition.categories?.data, [
definition.categories?.data "attributes",
).map((category) => category.attributes.short)} ] as const).map((category) => category.attributes.short)}
/> />
<br /> <br />
</> </>
) ))}
)}
</div> </div>
)} )}
</ContentPanel> </ContentPanel>
@ -194,14 +194,16 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk(); const sdk = getReadySdk();
const contents = await sdk.getWikiPagesSlugs(); const contents = await sdk.getWikiPagesSlugs();
const paths: GetStaticPathsResult["paths"] = []; const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.wikiPages?.data).map((wikiPage) => { filterHasAttributes(contents.wikiPages?.data, ["attributes"] as const).map(
(wikiPage) => {
context.locales?.map((local) => context.locales?.map((local) =>
paths.push({ paths.push({
params: { slug: wikiPage.attributes.slug }, params: { slug: wikiPage.attributes.slug },
locale: local, locale: local,
}) })
); );
}); }
);
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -74,7 +74,8 @@ const Chronology = ({
horizontalLine horizontalLine
/> />
{filterHasAttributes(chronologyEras).map((era) => ( {filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map(
(era) => (
<Fragment key={era.id}> <Fragment key={era.id}>
<NavOption <NavOption
url={`#${era.attributes.slug}`} url={`#${era.attributes.slug}`}
@ -89,7 +90,8 @@ const Chronology = ({
border border
/> />
</Fragment> </Fragment>
))} )
)}
</SubPanel> </SubPanel>
), ),
[chronologyEras, langui] [chronologyEras, langui]

View File

@ -120,9 +120,11 @@ const Wiki = ({
icon={Icon.ChevronLeft} icon={Icon.ChevronLeft}
/> />
)} )}
{filterHasAttributes(filteredPages).map((page) => ( {filterHasAttributes(filteredPages, [
"id",
"attributes.translations",
] as const).map((page) => (
<Fragment key={page.id}> <Fragment key={page.id}>
{page.attributes.translations && (
<TranslatedPreviewCard <TranslatedPreviewCard
href={`/wiki/${page.attributes.slug}`} href={`/wiki/${page.attributes.slug}`}
translations={page.attributes.translations.map( translations={page.attributes.translations.map(
@ -149,7 +151,6 @@ const Wiki = ({
(category) => category.attributes?.short ?? "" (category) => category.attributes?.short ?? ""
)} )}
/> />
)}
</Fragment> </Fragment>
))} ))}
</div> </div>