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
selectedScan.source_language?.data?.attributes?.code text={
? "Scan" selectedScan.language?.data?.attributes?.code ===
: "Scanlation"} selectedScan.source_language?.data?.attributes?.code
</Chip> ? "Scan"
: "Scanlation"
}
/>
</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",
<Fragment key={scanner.id}> "attributes",
<RecorderChip ] as const).map((scanner) => (
langui={langui} <Fragment key={scanner.id}>
recorder={scanner.attributes} <RecorderChip
/> langui={langui}
</Fragment> recorder={scanner.attributes}
) />
)} </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",
<Fragment key={cleaner.id}> "attributes",
<RecorderChip ] as const).map((cleaner) => (
langui={langui} <Fragment key={cleaner.id}>
recorder={cleaner.attributes} <RecorderChip
/> langui={langui}
</Fragment> recorder={cleaner.attributes}
) />
)} </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",
<Fragment key={typesetter.id}> "attributes",
<RecorderChip ] as const).map((typesetter) => (
langui={langui} <Fragment key={typesetter.id}>
recorder={typesetter.attributes} <RecorderChip
/> langui={langui}
</Fragment> recorder={typesetter.attributes}
) />
)} </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) => { getAssetURL(image.attributes.url, ImageQuality.Large)
if (isDefinedAndNotEmpty(image.attributes.url)) );
images.push(
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
selectedScan.source_language?.data?.attributes?.code text={
? "Scan" selectedScan.language?.data?.attributes?.code ===
: "Scanlation"} selectedScan.source_language?.data?.attributes?.code
</Chip> ? "Scan"
: "Scanlation"
}
/>
</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",
<Fragment key={scanner.id}> "attributes",
<RecorderChip ] as const).map((scanner) => (
langui={langui} <Fragment key={scanner.id}>
recorder={scanner.attributes} <RecorderChip
/> langui={langui}
</Fragment> recorder={scanner.attributes}
) />
)} </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",
<Fragment key={cleaner.id}> "attributes",
<RecorderChip ] as const).map((cleaner) => (
langui={langui} <Fragment key={cleaner.id}>
recorder={cleaner.attributes} <RecorderChip
/> langui={langui}
</Fragment> recorder={cleaner.attributes}
) />
)} </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",
<Fragment key={typesetter.id}> "attributes",
<RecorderChip ] as const).map((typesetter) => (
langui={langui} <Fragment key={typesetter.id}>
recorder={typesetter.attributes} <RecorderChip
/> langui={langui}
</Fragment> recorder={typesetter.attributes}
) />
)} </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",
<Fragment key={language.attributes.code}> ] as const).map((language) => (
<Chip>{language.attributes.code.toUpperCase()}</Chip> <Fragment key={language.attributes.code}>
</Fragment> <Chip text={language.attributes.code.toUpperCase()} />
) </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}
? `Recorder#${recorder.anonymous_code}` text={
: recorder.username} recorder.anonymize
</Chip> ? `Recorder#${recorder.anonymous_code}`
: recorder.username
}
/>
</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
groupItems.length <= 1 text={`${groupItems.length} ${
? langui.result?.toLowerCase() ?? "" groupItems.length <= 1
: langui.results?.toLowerCase() ?? "" ? langui.result?.toLowerCase() ?? ""
}`}</Chip> : langui.results?.toLowerCase() ?? ""
}`}
/>
</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) =>
if (isDefined(item)) { hasAttributes(item, paths)
const attributesToCheck = attributes ?? (Object.keys(item) as P[]); ) as unknown as SelectiveNonNullable<T, typeof paths[number]>[]);
return attributesToCheck.every((attribute) =>
isDefined(item[attribute]) const hasAttributes = <T>(item: T, paths: readonly PathDot<T>[]): boolean => {
); if (isDefined(item)) {
} return paths.every((path) => {
return false; const attributeToCheck = (path as string).split(".")[0];
}) as unknown as SelectiveRequiredNonNullable<NonNullable<T>, P>[]); return (
isDefined(attributeToCheck) &&
Object.keys(item).includes(attributeToCheck) &&
isDefined(item[attributeToCheck as keyof T])
);
});
}
return false;
};
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(
context.locales?.map((local) => { (video) => {
paths.push({ params: { uid: video.attributes.uid }, locale: local }); context.locales?.map((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,59 +224,60 @@ 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} className="mobile:w-[80%]"
className="mobile:w-[80%]" >
> <PreviewCard
<PreviewCard href={`/library/${libraryItem.attributes.slug}`}
href={`/library/${libraryItem.attributes.slug}`} title={libraryItem.attributes.title}
title={libraryItem.attributes.title} subtitle={libraryItem.attributes.subtitle}
subtitle={libraryItem.attributes.subtitle} thumbnail={
thumbnail={ libraryItem.attributes.thumbnail?.data?.attributes
libraryItem.attributes.thumbnail?.data?.attributes }
} thumbnailAspectRatio="21/29.7"
thumbnailAspectRatio="21/29.7" thumbnailRounded={false}
thumbnailRounded={false} topChips={
topChips={ libraryItem.attributes.metadata &&
libraryItem.attributes.metadata && libraryItem.attributes.metadata.length > 0 &&
libraryItem.attributes.metadata.length > 0 && libraryItem.attributes.metadata[0]
libraryItem.attributes.metadata[0] ? [
? [ prettyItemSubType(
prettyItemSubType( libraryItem.attributes.metadata[0]
libraryItem.attributes.metadata[0] ),
), ]
] : []
: [] }
} bottomChips={filterHasAttributes(
bottomChips={libraryItem.attributes.categories?.data.map( libraryItem.attributes.categories?.data,
(category) => category.attributes?.short ?? "" ["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,
price: libraryItem.attributes.price, price: libraryItem.attributes.price,
position: "Bottom", position: "Bottom",
}} }}
infoAppend={ infoAppend={
!isUntangibleGroupItem( !isUntangibleGroupItem(
libraryItem.attributes.metadata?.[0] libraryItem.attributes.metadata?.[0]
) && ( ) && (
<PreviewCardCTAs <PreviewCardCTAs
id={libraryItem.id} id={libraryItem.id}
langui={langui} langui={langui}
/> />
) )
} }
/> />
</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(
context.locales?.map((local) => { (item) => {
paths.push({ context.locales?.map((local) => {
params: { slug: item.attributes.slug }, paths.push({
locale: local, params: { slug: item.attributes.slug },
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,320 +137,322 @@ const testingContent = (contents: Props["contents"]): Report => {
lines: [], lines: [],
}; };
filterHasAttributes(contents.contents?.data).map((content) => { filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`; (content) => {
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`; 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}`;
if (content.attributes.categories?.data.length === 0) { if (content.attributes.categories?.data.length === 0) {
report.lines.push({ report.lines.push({
subitems: [content.attributes.slug], subitems: [content.attributes.slug],
name: "No Category", name: "No Category",
type: "Missing", type: "Missing",
severity: "Medium", severity: "Medium",
description: "The Content has no Category.", description: "The Content has no Category.",
recommandation: "Select a Category in relation with the Content", recommandation: "Select a Category in relation with the Content",
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} }
if (!content.attributes.type?.data?.id) { if (!content.attributes.type?.data?.id) {
report.lines.push({ report.lines.push({
subitems: [content.attributes.slug], subitems: [content.attributes.slug],
name: "No Category", name: "No Category",
type: "Missing", type: "Missing",
severity: "Medium", severity: "Medium",
description: "The Content has no Type.", description: "The Content has no Type.",
recommandation: 'If unsure, use the "Other" Type.', recommandation: 'If unsure, use the "Other" Type.',
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} }
if (content.attributes.ranged_contents?.data.length === 0) { if (content.attributes.ranged_contents?.data.length === 0) {
report.lines.push({ report.lines.push({
subitems: [content.attributes.slug], subitems: [content.attributes.slug],
name: "No Ranged Content", name: "No Ranged Content",
type: "Improvement", type: "Improvement",
severity: "Low", severity: "Low",
description: "The Content has no Ranged Content.", description: "The Content has no Ranged Content.",
recommandation: recommandation:
"If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).", "If this Content is available in one or multiple Library Item(s), create a Range Content to connect the Content to its Library Item(s).",
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} }
if (!content.attributes.thumbnail?.data?.id) { if (!content.attributes.thumbnail?.data?.id) {
report.lines.push({ report.lines.push({
subitems: [content.attributes.slug], subitems: [content.attributes.slug],
name: "No Thumbnail", name: "No Thumbnail",
type: "Missing", type: "Missing",
severity: "High", severity: "High",
description: "The Content has no Thumbnail.", description: "The Content has no Thumbnail.",
recommandation: "", recommandation: "",
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} }
if (content.attributes.translations?.length === 0) { if (content.attributes.translations?.length === 0) {
report.lines.push({ report.lines.push({
subitems: [content.attributes.slug], subitems: [content.attributes.slug],
name: "No Titles", name: "No Titles",
type: "Missing", type: "Missing",
severity: "High", severity: "High",
description: "The Content has no Titles.", description: "The Content has no Titles.",
recommandation: "", recommandation: "",
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} else { } else {
const titleLanguages: string[] = []; const titleLanguages: string[] = [];
filterDefined(content.attributes.translations).map( filterDefined(content.attributes.translations).map(
(translation, titleIndex) => { (translation, titleIndex) => {
if (translation.language?.data?.id) { if (translation.language?.data?.id) {
if (translation.language.data.id in titleLanguages) { if (translation.language.data.id in titleLanguages) {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "Duplicate Language",
type: "Error",
severity: "High",
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
titleLanguages.push(translation.language.data.id);
}
} else {
report.lines.push({ report.lines.push({
subitems: [ subitems: [
content.attributes.slug, content.attributes.slug,
`Title ${titleIndex.toString()}`, `Title ${titleIndex.toString()}`,
], ],
name: "Duplicate Language", name: "No Language",
type: "Error", type: "Error",
severity: "High", severity: "Very High",
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!translation.description) {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "No Description",
type: "Missing",
severity: "Medium",
description: "", description: "",
recommandation: "", recommandation: "",
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} else {
titleLanguages.push(translation.language.data.id);
} }
} else {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "No Language",
type: "Error",
severity: "Very High",
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!translation.description) {
report.lines.push({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "No Description",
type: "Missing",
severity: "Medium",
description: "",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (translation.text_set) { if (translation.text_set) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Text Set",
type: "Missing",
severity: "Medium",
description: "The Content has no Text Set.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
/*
*const textSetLanguages: string[] = [];
*if (content.attributes && textSet) {
* if (textSet.language?.data?.id) {
* if (textSet.language.data.id in textSetLanguages) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Duplicate Language",
* type: "Error",
* severity: "High",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* } else {
* textSetLanguages.push(textSet.language.data.id);
* }
* } else {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Language",
* type: "Error",
* severity: "Very High",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (!textSet.source_language?.data?.id) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Source Language",
* type: "Error",
* severity: "High",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (textSet.status !== Enum_Componentsetstextset_Status.Done) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Not Done Status",
* type: "Improvement",
* severity: "Low",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (!textSet.text || textSet.text.length < 10) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Text",
* type: "Missing",
* severity: "Medium",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (
* textSet.source_language?.data?.id ===
* textSet.language?.data?.id
* ) {
* if (textSet.transcribers?.data.length === 0) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Transcribers",
* type: "Missing",
* severity: "High",
* description:
* "The Content is a Transcription but doesn't credit any Transcribers.",
* recommandation: "Add the appropriate Transcribers.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* if (
* textSet.translators?.data &&
* textSet.translators.data.length > 0
* ) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Credited Translators",
* type: "Error",
* severity: "High",
* description:
* "The Content is a Transcription but credits one or more Translators.",
* recommandation:
* "If appropriate, create a Translation Text Set with the Translator credited there.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* } else {
* if (textSet.translators?.data.length === 0) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Translators",
* type: "Missing",
* severity: "High",
* description:
* "The Content is a Transcription but doesn't credit any Translators.",
* recommandation: "Add the appropriate Translators.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* if (
* textSet.transcribers?.data &&
* textSet.transcribers.data.length > 0
* ) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Credited Transcribers",
* type: "Error",
* severity: "High",
* description:
* "The Content is a Translation but credits one or more Transcribers.",
* recommandation:
* "If appropriate, create a Transcription Text Set with the Transcribers credited there.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* }
*}
*/
}
report.lines.push({ report.lines.push({
subitems: [content.attributes.slug], subitems: [content.attributes.slug],
name: "No Text Set", name: "No Sets",
type: "Missing", type: "Missing",
severity: "Medium", severity: "Medium",
description: "The Content has no Text Set.", description: "The Content has no Sets.",
recommandation: "", recommandation: "",
backendUrl: backendUrl, backendUrl: backendUrl,
frontendUrl: frontendUrl, frontendUrl: frontendUrl,
}); });
} else {
/*
*const textSetLanguages: string[] = [];
*if (content.attributes && textSet) {
* if (textSet.language?.data?.id) {
* if (textSet.language.data.id in textSetLanguages) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Duplicate Language",
* type: "Error",
* severity: "High",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* } else {
* textSetLanguages.push(textSet.language.data.id);
* }
* } else {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Language",
* type: "Error",
* severity: "Very High",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (!textSet.source_language?.data?.id) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Source Language",
* type: "Error",
* severity: "High",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (textSet.status !== Enum_Componentsetstextset_Status.Done) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Not Done Status",
* type: "Improvement",
* severity: "Low",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (!textSet.text || textSet.text.length < 10) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Text",
* type: "Missing",
* severity: "Medium",
* description: "",
* recommandation: "",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
*
* if (
* textSet.source_language?.data?.id ===
* textSet.language?.data?.id
* ) {
* if (textSet.transcribers?.data.length === 0) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Transcribers",
* type: "Missing",
* severity: "High",
* description:
* "The Content is a Transcription but doesn't credit any Transcribers.",
* recommandation: "Add the appropriate Transcribers.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* if (
* textSet.translators?.data &&
* textSet.translators.data.length > 0
* ) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Credited Translators",
* type: "Error",
* severity: "High",
* description:
* "The Content is a Transcription but credits one or more Translators.",
* recommandation:
* "If appropriate, create a Translation Text Set with the Translator credited there.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* } else {
* if (textSet.translators?.data.length === 0) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "No Translators",
* type: "Missing",
* severity: "High",
* description:
* "The Content is a Transcription but doesn't credit any Translators.",
* recommandation: "Add the appropriate Translators.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* if (
* textSet.transcribers?.data &&
* textSet.transcribers.data.length > 0
* ) {
* report.lines.push({
* subitems: [
* content.attributes.slug,
* `TextSet ${textSetIndex.toString()}`,
* ],
* name: "Credited Transcribers",
* type: "Error",
* severity: "High",
* description:
* "The Content is a Translation but credits one or more Transcribers.",
* recommandation:
* "If appropriate, create a Transcription Text Set with the Transcribers credited there.",
* backendUrl: backendUrl,
* frontendUrl: frontendUrl,
* });
* }
* }
*}
*/
} }
);
report.lines.push({ }
subitems: [content.attributes.slug],
name: "No Sets",
type: "Missing",
severity: "Medium",
description: "The Content has no Sets.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
);
} }
}); );
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,15 +221,17 @@ 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(
<Fragment key={index}> (url, index) => (
<Button <Fragment key={index}>
href={url.url} <Button
target={"_blank"} href={url.url}
text={prettyURL(url.url)} target={"_blank"}
/> text={prettyURL(url.url)}
</Fragment> />
))} </Fragment>
)
)}
</div> </div>
) : ( ) : (
<p>{langui.item_not_available}</p> <p>{langui.item_not_available}</p>
@ -246,33 +248,32 @@ 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",
<Fragment key={galleryItem.id}> "attributes",
<div ] as const).map((galleryItem, index) => (
className="relative aspect-square cursor-pointer <Fragment key={galleryItem.id}>
<div
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,
).map((image) => ["attributes"] as const
getAssetURL( ).map((image) =>
image.attributes.url, getAssetURL(image.attributes.url, ImageQuality.Large)
ImageQuality.Large );
) openLightBox(images, index);
); }}
openLightBox(images, index); >
}} <Img
> className="h-full w-full rounded-lg
<Img
className="h-full w-full rounded-lg
bg-light object-cover drop-shadow-shade-md" bg-light object-cover drop-shadow-shade-md"
image={galleryItem.attributes} image={galleryItem.attributes}
/> />
</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,56 +512,57 @@ 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",
<ContentLine ] as const).map((rangedContent) => (
content={ <ContentLine
rangedContent.attributes.content?.data?.attributes content={
? { rangedContent.attributes.content?.data?.attributes
translations: filterDefined( ? {
translations: filterDefined(
rangedContent.attributes.content.data.attributes
.translations
).map((translation) => ({
pre_title: translation.pre_title,
title: translation.title,
subtitle: translation.subtitle,
language:
translation.language?.data?.attributes?.code,
})),
categories: filterHasAttributes(
rangedContent.attributes.content.data.attributes
.categories?.data,
["attributes"]
).map((category) => category.attributes.short),
type:
rangedContent.attributes.content.data.attributes
.type?.data?.attributes?.titles?.[0]?.title ??
prettySlug(
rangedContent.attributes.content.data.attributes rangedContent.attributes.content.data.attributes
.translations .type?.data?.attributes?.slug
).map((translation) => ({ ),
pre_title: translation.pre_title, slug: rangedContent.attributes.content.data
title: translation.title, .attributes.slug,
subtitle: translation.subtitle, }
language: : undefined
translation.language?.data?.attributes?.code, }
})), langui={langui}
categories: filterHasAttributes( rangeStart={
rangedContent.attributes.content.data.attributes rangedContent.attributes.range[0]?.__typename ===
.categories?.data "ComponentRangePageRange"
).map((category) => category.attributes.short), ? `${rangedContent.attributes.range[0].starting_page}`
type: : ""
rangedContent.attributes.content.data.attributes }
.type?.data?.attributes?.titles?.[0]?.title ?? slug={rangedContent.attributes.slug}
prettySlug( parentSlug={item.slug}
rangedContent.attributes.content.data key={rangedContent.id}
.attributes.type?.data?.attributes?.slug languages={languages}
), hasScanSet={
slug: rangedContent.attributes.content.data isDefined(rangedContent.attributes.scan_set) &&
.attributes.slug, rangedContent.attributes.scan_set.length > 0
} }
: undefined />
} ))}
langui={langui}
rangeStart={
rangedContent.attributes.range[0]?.__typename ===
"ComponentRangePageRange"
? `${rangedContent.attributes.range[0].starting_page}`
: ""
}
slug={rangedContent.attributes.slug}
parentSlug={item.slug}
key={rangedContent.id}
languages={languages}
hasScanSet={
isDefined(rangedContent.attributes.scan_set) &&
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(
context.locales?.map((local) => (item) => {
paths.push({ params: { slug: item.attributes.slug }, locale: local }) context.locales?.map((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 <>
key={index} <DefinitionCard
source={definition.source?.data?.attributes?.name} key={index}
translations={filterHasAttributes( source={definition.source?.data?.attributes?.name}
definition.translations translations={definition.translations.map((translation) => ({
).map((translation) => ({ language: translation?.language?.data?.attributes?.code,
language: translation.language.data?.attributes?.code, definition: translation?.definition,
definition: translation.definition, status: translation?.status,
status: translation.status, }))}
}))} index={index + 1}
index={index + 1} languages={languages}
languages={languages} langui={langui}
langui={langui} categories={filterHasAttributes(definition.categories?.data, [
categories={filterHasAttributes( "attributes",
definition.categories?.data ] as const).map((category) => category.attributes.short)}
).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(
context.locales?.map((local) => (wikiPage) => {
paths.push({ context.locales?.map((local) =>
params: { slug: wikiPage.attributes.slug }, paths.push({
locale: local, params: { slug: wikiPage.attributes.slug },
}) locale: local,
); })
}); );
}
);
return { return {
paths, paths,
fallback: "blocking", fallback: "blocking",

View File

@ -74,22 +74,24 @@ const Chronology = ({
horizontalLine horizontalLine
/> />
{filterHasAttributes(chronologyEras).map((era) => ( {filterHasAttributes(chronologyEras, ["attributes", "id"] as const).map(
<Fragment key={era.id}> (era) => (
<NavOption <Fragment key={era.id}>
url={`#${era.attributes.slug}`} <NavOption
title={ url={`#${era.attributes.slug}`}
era.attributes.title && title={
era.attributes.title.length > 0 && era.attributes.title &&
era.attributes.title[0] era.attributes.title.length > 0 &&
? era.attributes.title[0].title era.attributes.title[0]
: prettySlug(era.attributes.slug) ? era.attributes.title[0].title
} : prettySlug(era.attributes.slug)
subtitle={`${era.attributes.starting_year}${era.attributes.ending_year}`} }
border subtitle={`${era.attributes.starting_year}${era.attributes.ending_year}`}
/> border
</Fragment> />
))} </Fragment>
)
)}
</SubPanel> </SubPanel>
), ),
[chronologyEras, langui] [chronologyEras, langui]

View File

@ -120,36 +120,37 @@ 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( (translation) => ({
(translation) => ({ title: translation?.title,
title: translation?.title, subtitle:
subtitle: translation?.aliases && translation.aliases.length > 0
translation?.aliases && translation.aliases.length > 0 ? translation.aliases
? translation.aliases .map((alias) => alias?.alias)
.map((alias) => alias?.alias) .join(" | ")
.join(" | ") : undefined,
: undefined, description: translation?.summary,
description: translation?.summary, language: translation?.language?.data?.attributes?.code,
language: translation?.language?.data?.attributes?.code, })
}) )}
)} thumbnail={page.attributes.thumbnail?.data?.attributes}
thumbnail={page.attributes.thumbnail?.data?.attributes} thumbnailAspectRatio={"4/3"}
thumbnailAspectRatio={"4/3"} thumbnailRounded
thumbnailRounded thumbnailForceAspectRatio
thumbnailForceAspectRatio languages={languages}
languages={languages} slug={page.attributes.slug}
slug={page.attributes.slug} keepInfoVisible={keepInfoVisible}
keepInfoVisible={keepInfoVisible} bottomChips={page.attributes.categories?.data.map(
bottomChips={page.attributes.categories?.data.map( (category) => category.attributes?.short ?? ""
(category) => category.attributes?.short ?? "" )}
)} />
/>
)}
</Fragment> </Fragment>
))} ))}
</div> </div>