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;
}, [router.locale, router.locales]);
const currencyOptions = useMemo(() => {
const memo: string[] = [];
filterHasAttributes(currencies).map((currentCurrency) => {
if (isDefinedAndNotEmpty(currentCurrency.attributes.code))
memo.push(currentCurrency.attributes.code);
});
return memo;
}, [currencies]);
const currencyOptions = useMemo(
() =>
filterHasAttributes(currencies, ["attributes"] as const).map(
(currentCurrency) => currentCurrency.attributes.code
),
[currencies]
);
const [currencySelect, setCurrencySelect] = useState<number>(-1);

View File

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

View File

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

View File

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

View File

@ -104,7 +104,7 @@ export const PostPage = ({
)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.status}</Chip>
<Chip text={selectedTranslation.status} />
</ToolTip>
</div>
)}
@ -113,7 +113,10 @@ export const PostPage = ({
<div>
<p className="font-headers font-bold">{"Authors"}:</p>
<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}>
<RecorderChip
langui={langui}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
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}>
<PreviewCard
href={`/archives/videos/v/${video.attributes.uid}`}
@ -152,7 +154,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const channels = await sdk.getVideoChannelsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
if (channels.videoChannels?.data)
filterHasAttributes(channels.videoChannels.data).map((channel) => {
filterHasAttributes(channels.videoChannels.data, [
"attributes",
] as const).map((channel) => {
context.locales?.map((local) => {
paths.push({
params: { uid: channel.attributes.uid },

View File

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

View File

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

View File

@ -114,13 +114,13 @@ const Content = ({
<p className="font-headers font-bold">
{langui.source_language}:
</p>
<Chip>
{prettyLanguage(
<Chip
text={prettyLanguage(
selectedTranslation.text_set.source_language.data.attributes
.code,
languages
)}
</Chip>
/>
</div>
)}
@ -134,7 +134,7 @@ const Content = ({
)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.text_set.status}</Chip>
<Chip text={selectedTranslation.text_set.status} />
</ToolTip>
</div>
@ -146,7 +146,8 @@ const Content = ({
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.transcribers.data
selectedTranslation.text_set.transcribers.data,
["attributes", "id"] as const
).map((recorder) => (
<Fragment key={recorder.id}>
<RecorderChip
@ -167,7 +168,8 @@ const Content = ({
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.translators.data
selectedTranslation.text_set.translators.data,
["attributes", "id"] as const
).map((recorder) => (
<Fragment key={recorder.id}>
<RecorderChip
@ -188,7 +190,8 @@ const Content = ({
</p>
<div className="grid place-content-center place-items-center gap-2">
{filterHasAttributes(
selectedTranslation.text_set.proofreaders.data
selectedTranslation.text_set.proofreaders.data,
["attributes", "id"] as const
).map((recorder) => (
<Fragment key={recorder.id}>
<RecorderChip
@ -221,59 +224,60 @@ const Content = ({
{langui.source}
</p>
<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 =
rangedContent.attributes?.library_item?.data;
if (libraryItem?.attributes && libraryItem.id) {
return (
<div
key={libraryItem.attributes.slug}
className="mobile:w-[80%]"
>
<PreviewCard
href={`/library/${libraryItem.attributes.slug}`}
title={libraryItem.attributes.title}
subtitle={libraryItem.attributes.subtitle}
thumbnail={
libraryItem.attributes.thumbnail?.data?.attributes
}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
topChips={
libraryItem.attributes.metadata &&
libraryItem.attributes.metadata.length > 0 &&
libraryItem.attributes.metadata[0]
? [
prettyItemSubType(
libraryItem.attributes.metadata[0]
),
]
: []
}
bottomChips={libraryItem.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
metadata={{
currencies: currencies,
release_date: libraryItem.attributes.release_date,
price: libraryItem.attributes.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(
libraryItem.attributes.metadata?.[0]
) && (
<PreviewCardCTAs
id={libraryItem.id}
langui={langui}
/>
)
}
/>
</div>
);
}
return <></>;
rangedContent.attributes.library_item.data;
return (
<div
key={libraryItem.attributes.slug}
className="mobile:w-[80%]"
>
<PreviewCard
href={`/library/${libraryItem.attributes.slug}`}
title={libraryItem.attributes.title}
subtitle={libraryItem.attributes.subtitle}
thumbnail={
libraryItem.attributes.thumbnail?.data?.attributes
}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
topChips={
libraryItem.attributes.metadata &&
libraryItem.attributes.metadata.length > 0 &&
libraryItem.attributes.metadata[0]
? [
prettyItemSubType(
libraryItem.attributes.metadata[0]
),
]
: []
}
bottomChips={filterHasAttributes(
libraryItem.attributes.categories?.data,
["attributes"] as const
).map((category) => category.attributes.short)}
metadata={{
currencies: currencies,
release_date: libraryItem.attributes.release_date,
price: libraryItem.attributes.price,
position: "Bottom",
}}
infoAppend={
!isUntangibleGroupItem(
libraryItem.attributes.metadata?.[0]
) && (
<PreviewCardCTAs
id={libraryItem.id}
langui={langui}
/>
)
}
/>
</div>
);
})}
</div>
</div>
@ -507,14 +511,16 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const contents = await sdk.getContentsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(contents.contents?.data).map((item) => {
context.locales?.map((local) => {
paths.push({
params: { slug: item.attributes.slug },
locale: local,
filterHasAttributes(contents.contents?.data, ["attributes"] as const).map(
(item) => {
context.locales?.map((local) => {
paths.push({
params: { slug: item.attributes.slug },
locale: local,
});
});
});
});
}
);
return {
paths,
fallback: "blocking",

View File

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

View File

@ -60,7 +60,7 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
/>
<p>{line.subitems.join(" -> ")}</p>
<p>{line.name}</p>
<Chip>{line.type}</Chip>
<Chip text={line.type} />
<Chip
className={
line.severity === "Very High"
@ -71,9 +71,8 @@ const CheckupContents = ({ contents, ...otherProps }: Props): JSX.Element => {
? "bg-[#fff344] !opacity-100"
: ""
}
>
{line.severity}
</Chip>
text={line.severity}
/>
<ToolTip content={line.recommandation} placement="left">
<p>{line.description}</p>
</ToolTip>
@ -138,320 +137,322 @@ const testingContent = (contents: Props["contents"]): Report => {
lines: [],
};
filterHasAttributes(contents.contents?.data).map((content) => {
const backendUrl = `${process.env.NEXT_PUBLIC_URL_CMS}/admin/content-manager/collectionType/api::content.content/${content.id}`;
const frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
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 frontendUrl = `${process.env.NEXT_PUBLIC_URL_SELF}/contents/${content.attributes.slug}`;
if (content.attributes.categories?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Category",
type: "Missing",
severity: "Medium",
description: "The Content has no Category.",
recommandation: "Select a Category in relation with the Content",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.categories?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Category",
type: "Missing",
severity: "Medium",
description: "The Content has no Category.",
recommandation: "Select a Category in relation with the Content",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.type?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Category",
type: "Missing",
severity: "Medium",
description: "The Content has no Type.",
recommandation: 'If unsure, use the "Other" Type.',
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.type?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Category",
type: "Missing",
severity: "Medium",
description: "The Content has no Type.",
recommandation: 'If unsure, use the "Other" Type.',
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.ranged_contents?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Ranged Content",
type: "Improvement",
severity: "Low",
description: "The Content has no Ranged Content.",
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).",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.ranged_contents?.data.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Ranged Content",
type: "Improvement",
severity: "Low",
description: "The Content has no Ranged Content.",
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).",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.thumbnail?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Thumbnail",
type: "Missing",
severity: "High",
description: "The Content has no Thumbnail.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (!content.attributes.thumbnail?.data?.id) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Thumbnail",
type: "Missing",
severity: "High",
description: "The Content has no Thumbnail.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
if (content.attributes.translations?.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Titles",
type: "Missing",
severity: "High",
description: "The Content has no Titles.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
const titleLanguages: string[] = [];
if (content.attributes.translations?.length === 0) {
report.lines.push({
subitems: [content.attributes.slug],
name: "No Titles",
type: "Missing",
severity: "High",
description: "The Content has no Titles.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
} else {
const titleLanguages: string[] = [];
filterDefined(content.attributes.translations).map(
(translation, titleIndex) => {
if (translation.language?.data?.id) {
if (translation.language.data.id in titleLanguages) {
filterDefined(content.attributes.translations).map(
(translation, titleIndex) => {
if (translation.language?.data?.id) {
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({
subitems: [
content.attributes.slug,
`Title ${titleIndex.toString()}`,
],
name: "Duplicate Language",
name: "No Language",
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: "",
recommandation: "",
backendUrl: backendUrl,
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({
subitems: [content.attributes.slug],
name: "No Text Set",
name: "No Sets",
type: "Missing",
severity: "Medium",
description: "The Content has no Text Set.",
description: "The Content has no Sets.",
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({
subitems: [content.attributes.slug],
name: "No Sets",
type: "Missing",
severity: "Medium",
description: "The Content has no Sets.",
recommandation: "",
backendUrl: backendUrl,
frontendUrl: frontendUrl,
});
}
);
);
}
}
});
);
return report;
};

View File

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

View File

@ -221,15 +221,17 @@ const LibrarySlug = ({
{item.urls?.length ? (
<div className="flex flex-row place-items-center gap-3">
<p>{langui.available_at}</p>
{filterHasAttributes(item.urls).map((url, index) => (
<Fragment key={index}>
<Button
href={url.url}
target={"_blank"}
text={prettyURL(url.url)}
/>
</Fragment>
))}
{filterHasAttributes(item.urls, ["url"] as const).map(
(url, index) => (
<Fragment key={index}>
<Button
href={url.url}
target={"_blank"}
text={prettyURL(url.url)}
/>
</Fragment>
)
)}
</div>
) : (
<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
gap-8"
>
{filterHasAttributes(item.gallery.data).map(
(galleryItem, index) => (
<Fragment key={galleryItem.id}>
<div
className="relative aspect-square cursor-pointer
{filterHasAttributes(item.gallery.data, [
"id",
"attributes",
] as const).map((galleryItem, index) => (
<Fragment key={galleryItem.id}>
<div
className="relative aspect-square cursor-pointer
transition-transform hover:scale-[1.02]"
onClick={() => {
const images: string[] = filterHasAttributes(
item.gallery?.data
).map((image) =>
getAssetURL(
image.attributes.url,
ImageQuality.Large
)
);
openLightBox(images, index);
}}
>
<Img
className="h-full w-full rounded-lg
onClick={() => {
const images: string[] = filterHasAttributes(
item.gallery?.data,
["attributes"] as const
).map((image) =>
getAssetURL(image.attributes.url, ImageQuality.Large)
);
openLightBox(images, index);
}}
>
<Img
className="h-full w-full rounded-lg
bg-light object-cover drop-shadow-shade-md"
image={galleryItem.attributes}
/>
</div>
</Fragment>
)
)}
image={galleryItem.attributes}
/>
</div>
</Fragment>
))}
</div>
</div>
)}
@ -288,9 +289,9 @@ const LibrarySlug = ({
<div className="grid place-content-start place-items-center">
<h3 className="text-xl">{langui.type}</h3>
<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>
)}
@ -331,8 +332,10 @@ const LibrarySlug = ({
<div className="flex flex-col place-items-center gap-2">
<h3 className="text-xl">{langui.categories}</h3>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{item.categories.data.map((category) => (
<Chip key={category.id}>{category.attributes?.name}</Chip>
{filterHasAttributes(item.categories.data, [
"attributes",
] as const).map((category) => (
<Chip key={category.id} text={category.attributes.name} />
))}
</div>
</div>
@ -458,7 +461,10 @@ const LibrarySlug = ({
className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))]
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}>
<PreviewCard
href={`/library/${subitem.attributes.slug}`}
@ -506,56 +512,57 @@ const LibrarySlug = ({
/>
)}
<div className="grid w-full gap-4">
{filterHasAttributes(item.contents.data).map(
(rangedContent) => (
<ContentLine
content={
rangedContent.attributes.content?.data?.attributes
? {
translations: filterDefined(
{filterHasAttributes(item.contents.data, [
"attributes",
] as const).map((rangedContent) => (
<ContentLine
content={
rangedContent.attributes.content?.data?.attributes
? {
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
.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
).map((category) => category.attributes.short),
type:
rangedContent.attributes.content.data.attributes
.type?.data?.attributes?.titles?.[0]?.title ??
prettySlug(
rangedContent.attributes.content.data
.attributes.type?.data?.attributes?.slug
),
slug: rangedContent.attributes.content.data
.attributes.slug,
}
: 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
}
/>
)
)}
.type?.data?.attributes?.slug
),
slug: rangedContent.attributes.content.data
.attributes.slug,
}
: 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>
)}
@ -643,7 +650,9 @@ export const getStaticPaths: GetStaticPaths = async (context) => {
const sdk = getReadySdk();
const libraryItems = await sdk.getLibraryItemsSlugs();
const paths: GetStaticPathsResult["paths"] = [];
filterHasAttributes(libraryItems.libraryItems?.data).map((item) => {
filterHasAttributes(libraryItems.libraryItems?.data, [
"attributes",
] as const).map((item) => {
context.locales?.map((local) =>
paths.push({ params: { slug: item.attributes.slug }, locale: local })
);
@ -702,8 +711,6 @@ const ContentLine = ({
),
});
console.log(prettySlug(slug, parentSlug));
return (
<div
className={cJoin(
@ -730,13 +737,13 @@ const ContentLine = ({
</a>
<div className="flex flex-row flex-wrap gap-1">
{content?.categories?.map((category, index) => (
<Chip key={index}>{category}</Chip>
<Chip key={index} text={category} />
))}
</div>
<p className="h-4 w-full border-b-2 border-dotted border-black opacity-30"></p>
<p>{rangeStart}</p>
{content?.type && (
<Chip className="justify-self-end thin:hidden">{content.type}</Chip>
<Chip className="justify-self-end thin:hidden" text={content.type} />
)}
</div>
<div

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -120,36 +120,37 @@ const Wiki = ({
icon={Icon.ChevronLeft}
/>
)}
{filterHasAttributes(filteredPages).map((page) => (
{filterHasAttributes(filteredPages, [
"id",
"attributes.translations",
] as const).map((page) => (
<Fragment key={page.id}>
{page.attributes.translations && (
<TranslatedPreviewCard
href={`/wiki/${page.attributes.slug}`}
translations={page.attributes.translations.map(
(translation) => ({
title: translation?.title,
subtitle:
translation?.aliases && translation.aliases.length > 0
? translation.aliases
.map((alias) => alias?.alias)
.join(" | ")
: undefined,
description: translation?.summary,
language: translation?.language?.data?.attributes?.code,
})
)}
thumbnail={page.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio={"4/3"}
thumbnailRounded
thumbnailForceAspectRatio
languages={languages}
slug={page.attributes.slug}
keepInfoVisible={keepInfoVisible}
bottomChips={page.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
/>
)}
<TranslatedPreviewCard
href={`/wiki/${page.attributes.slug}`}
translations={page.attributes.translations.map(
(translation) => ({
title: translation?.title,
subtitle:
translation?.aliases && translation.aliases.length > 0
? translation.aliases
.map((alias) => alias?.alias)
.join(" | ")
: undefined,
description: translation?.summary,
language: translation?.language?.data?.attributes?.code,
})
)}
thumbnail={page.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio={"4/3"}
thumbnailRounded
thumbnailForceAspectRatio
languages={languages}
slug={page.attributes.slug}
keepInfoVisible={keepInfoVisible}
bottomChips={page.attributes.categories?.data.map(
(category) => category.attributes?.short ?? ""
)}
/>
</Fragment>
))}
</div>