diff --git a/next.config.js b/next.config.js index d5e2d95..4646531 100644 --- a/next.config.js +++ b/next.config.js @@ -27,7 +27,7 @@ module.exports = { }, { source: "/gallery", - destination: "https://gallery.accords-library.com/", + destination: "https://gallery.accords-library.com/posts", permanent: false, }, ]; diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index 36d7e45..c8df188 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -5,7 +5,13 @@ import { AppStaticProps } from "graphql/getAppStaticProps"; import { cIf, cJoin } from "helpers/className"; import { prettyLanguage, prettySlug } from "helpers/formatters"; import { getOgImage, ImageQuality } from "helpers/img"; -import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; +import { + filterHasAttributes, + isDefined, + isDefinedAndNotEmpty, + isUndefined, + iterateMap, +} from "helpers/others"; // import { getClient, Indexes, search, SearchResult } from "helpers/search"; import { useMediaMobile } from "hooks/useMediaQuery"; @@ -19,6 +25,7 @@ import { ButtonGroup } from "./Inputs/ButtonGroup"; import { OrderableList } from "./Inputs/OrderableList"; import { Select } from "./Inputs/Select"; import { TextInput } from "./Inputs/TextInput"; +import { ContentPlaceholder } from "./PanelComponents/ContentPlaceholder"; import { MainPanel } from "./Panels/MainPanel"; import { Popup } from "./Popup"; @@ -98,7 +105,7 @@ export function AppLayout(props: Props): JSX.Element { if (SwipeEventData.velocity < SENSIBILITY_SWIPE) return; if (mainPanelOpen === true) { setMainPanelOpen(false); - } else if (subPanel === true && contentPanel === true) { + } else if (isDefined(subPanel) && isDefined(contentPanel)) { setSubPanelOpen(true); } } @@ -116,7 +123,7 @@ export function AppLayout(props: Props): JSX.Element { }); const turnSubIntoContent = useMemo( - () => isDefined(subPanel) && isDefined(contentPanel), + () => isDefined(subPanel) && isUndefined(contentPanel), [contentPanel, subPanel] ); @@ -173,11 +180,8 @@ export function AppLayout(props: Props): JSX.Element { const currencyOptions = useMemo(() => { const list: string[] = []; - currencies.map((currentCurrency) => { - if ( - currentCurrency.attributes && - isDefinedAndNotEmpty(currentCurrency.attributes.code) - ) + filterHasAttributes(currencies).map((currentCurrency) => { + if (isDefinedAndNotEmpty(currentCurrency.attributes.code)) list.push(currentCurrency.attributes.code); }); return list; @@ -283,15 +287,10 @@ export function AppLayout(props: Props): JSX.Element { {isDefined(contentPanel) ? ( contentPanel ) : ( -
-
-

-

{langui.select_option_sidebar}

-
-
+ )} @@ -299,11 +298,10 @@ export function AppLayout(props: Props): JSX.Element { {isDefined(subPanel) && (
@@ -399,8 +397,9 @@ export function AppLayout(props: Props): JSX.Element { ]) } onChange={(items) => { - const newPreferredLanguages = [...items].map( - ([code]) => code + const newPreferredLanguages = iterateMap( + items, + (code) => code ); setPreferredLanguages(newPreferredLanguages); if (router.locale !== newPreferredLanguages[0]) { diff --git a/src/components/Inputs/Button.tsx b/src/components/Inputs/Button.tsx index 1d955c4..314c04c 100644 --- a/src/components/Inputs/Button.tsx +++ b/src/components/Inputs/Button.tsx @@ -63,9 +63,8 @@ export function Button(props: Props): JSX.Element { text-dark transition-all`, cIf( active, - "!border-black bg-black text-light drop-shadow-black-lg", - `cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg - active:border-black active:bg-black active:text-light active:drop-shadow-black-lg` + "!border-black bg-black !text-light drop-shadow-black-lg", + "cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg" ), className )} diff --git a/src/components/Inputs/LanguageSwitcher.tsx b/src/components/Inputs/LanguageSwitcher.tsx index 9f9316d..ff93e0d 100644 --- a/src/components/Inputs/LanguageSwitcher.tsx +++ b/src/components/Inputs/LanguageSwitcher.tsx @@ -2,6 +2,7 @@ import { Icon } from "components/Ico"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { cJoin } from "helpers/className"; import { prettyLanguage } from "helpers/formatters"; +import { iterateMap } from "helpers/others"; import { Fragment } from "react"; import { ToolTip } from "../ToolTip"; @@ -22,15 +23,13 @@ export function LanguageSwitcher(props: Props): JSX.Element { - {[...locales].map(([locale, value], index) => ( + {iterateMap(locales, (locale, value, index) => ( - {locale && ( -
diff --git a/src/components/Inputs/OrderableList.tsx b/src/components/Inputs/OrderableList.tsx index f6f5eed..1c913a3 100644 --- a/src/components/Inputs/OrderableList.tsx +++ b/src/components/Inputs/OrderableList.tsx @@ -1,5 +1,5 @@ import { Ico, Icon } from "components/Ico"; -import { arrayMove, isDefinedAndNotEmpty } from "helpers/others"; +import { isDefinedAndNotEmpty, iterateMap, mapMoveEntry } from "helpers/others"; import { Fragment, useCallback, useState } from "react"; @@ -16,17 +16,16 @@ export function OrderableList(props: Props): JSX.Element { const updateOrder = useCallback( (sourceIndex: number, targetIndex: number) => { - const newItems = arrayMove([...items], sourceIndex, targetIndex); - const map = new Map(newItems); - setItems(map); - onChange?.(map); + const newItems = mapMoveEntry(items, sourceIndex, targetIndex); + setItems(newItems); + onChange?.(newItems); }, [items, onChange] ); return (
- {[...items].map(([key, value], index) => ( + {iterateMap(items, (key, value, index) => ( {props.insertLabels && isDefinedAndNotEmpty(props.insertLabels.get(index)) && ( diff --git a/src/components/Inputs/Select.tsx b/src/components/Inputs/Select.tsx index 43a5edb..7d08768 100644 --- a/src/components/Inputs/Select.tsx +++ b/src/components/Inputs/Select.tsx @@ -43,7 +43,7 @@ export function Select(props: Props): JSX.Element { className="!text-xs" onClick={() => { setState(-1); - toggleOpened(); + setOpened(false); }} /> )} diff --git a/src/components/Inputs/Switch.tsx b/src/components/Inputs/Switch.tsx index a7f90cf..f35526c 100644 --- a/src/components/Inputs/Switch.tsx +++ b/src/components/Inputs/Switch.tsx @@ -11,7 +11,7 @@ interface Props { } export function Switch(props: Props): JSX.Element { - const { state, setState, className, disabled } = props; + const { state, setState, className, disabled = false } = props; const toggleState = useToggle(setState); return (
{ - if (disabled === false) toggleState(); + if (!disabled) toggleState(); }} >
- {content.attributes.content?.data?.attributes?.categories?.data.map( - (category) => ( - {category.attributes?.short} - ) - )} + {filterHasAttributes( + content.attributes.content?.data?.attributes?.categories?.data + ).map((category) => ( + {category.attributes.short} + ))}

diff --git a/src/components/Library/ScanSet.tsx b/src/components/Library/ScanSet.tsx index 6d3e7c0..5495d64 100644 --- a/src/components/Library/ScanSet.tsx +++ b/src/components/Library/ScanSet.tsx @@ -7,10 +7,15 @@ import { GetLibraryItemScansQuery } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { getAssetFilename, getAssetURL, ImageQuality } from "helpers/img"; import { isInteger } from "helpers/numbers"; -import { getStatusDescription, isDefinedAndNotEmpty } from "helpers/others"; +import { + filterHasAttributes, + getStatusDescription, + isDefined, + isDefinedAndNotEmpty, +} from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; -import { Fragment } from "react"; +import { Fragment, useMemo } from "react"; interface Props { openLightBox: (images: string[], index?: number) => void; @@ -81,9 +86,14 @@ export function ScanSet(props: Props): JSX.Element { }, }); + const pages = useMemo( + () => selectedScan && filterHasAttributes(selectedScan.pages?.data), + [selectedScan] + ); + return ( <> - {selectedScan && ( + {selectedScan && isDefined(pages) && (

{"Scanners"}:

- {selectedScan.scanners.data.map((scanner) => ( - - {scanner.attributes && ( + {filterHasAttributes(selectedScan.scanners.data).map( + (scanner) => ( + - )} - - ))} + + ) + )}
)} @@ -144,16 +154,18 @@ export function ScanSet(props: Props): JSX.Element {

{"Cleaners"}:

- {selectedScan.cleaners.data.map((cleaner) => ( - - {cleaner.attributes && ( - - )} - - ))} + {filterHasAttributes(selectedScan.cleaners.data).map( + (cleaner) => ( + + {cleaner.attributes && ( + + )} + + ) + )}
)} @@ -163,16 +175,18 @@ export function ScanSet(props: Props): JSX.Element {

{"Typesetters"}:

- {selectedScan.typesetters.data.map((typesetter) => ( - - {typesetter.attributes && ( - - )} - - ))} + {filterHasAttributes(selectedScan.typesetters.data).map( + (typesetter) => ( + + {typesetter.attributes && ( + + )} + + ) + )}
)} @@ -188,18 +202,15 @@ export function ScanSet(props: Props): JSX.Element { className="grid items-end gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(10rem,1fr))] mobile:grid-cols-2" > - {selectedScan.pages?.data.map((page, index) => ( + {pages.map((page, index) => (
{ const images: string[] = []; - selectedScan.pages?.data.map((image) => { - if ( - image.attributes && - isDefinedAndNotEmpty(image.attributes.url) - ) + pages.map((image) => { + if (isDefinedAndNotEmpty(image.attributes.url)) images.push( getAssetURL(image.attributes.url, ImageQuality.Large) ); diff --git a/src/components/Library/ScanSetCover.tsx b/src/components/Library/ScanSetCover.tsx index 5d346d9..485fba4 100644 --- a/src/components/Library/ScanSetCover.tsx +++ b/src/components/Library/ScanSetCover.tsx @@ -8,7 +8,7 @@ import { } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { getAssetURL, ImageQuality } from "helpers/img"; -import { getStatusDescription } from "helpers/others"; +import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { Fragment } from "react"; @@ -87,16 +87,16 @@ export function ScanSetCover(props: Props): JSX.Element {

{"Scanners"}:

- {selectedScan.scanners.data.map((scanner) => ( - - {scanner.attributes && ( + {filterHasAttributes(selectedScan.scanners.data).map( + (scanner) => ( + - )} - - ))} + + ) + )}
)} @@ -105,16 +105,16 @@ export function ScanSetCover(props: Props): JSX.Element {

{"Cleaners"}:

- {selectedScan.cleaners.data.map((cleaner) => ( - - {cleaner.attributes && ( + {filterHasAttributes(selectedScan.cleaners.data).map( + (cleaner) => ( + - )} - - ))} + + ) + )}
)} @@ -124,16 +124,16 @@ export function ScanSetCover(props: Props): JSX.Element {

{"Typesetters"}:

- {selectedScan.typesetters.data.map((typesetter) => ( - - {typesetter.attributes && ( + {filterHasAttributes(selectedScan.typesetters.data).map( + (typesetter) => ( + - )} - - ))} + + ) + )}
)} diff --git a/src/components/PanelComponents/ContentPlaceholder.tsx b/src/components/PanelComponents/ContentPlaceholder.tsx new file mode 100644 index 0000000..935735b --- /dev/null +++ b/src/components/PanelComponents/ContentPlaceholder.tsx @@ -0,0 +1,30 @@ +import { Ico, Icon } from "components/Ico"; +import { cIf, cJoin } from "helpers/className"; +import { isDefined } from "helpers/others"; + +interface Props { + message: string; + icon?: Icon; +} + +export function ContentPlaceholder(props: Props): JSX.Element { + const { message, icon } = props; + return ( +
+
+ {isDefined(icon) && } +

+ {message} +

+
+
+ ); +} diff --git a/src/components/PanelComponents/NavOption.tsx b/src/components/PanelComponents/NavOption.tsx index 53ab3d3..e616edf 100644 --- a/src/components/PanelComponents/NavOption.tsx +++ b/src/components/PanelComponents/NavOption.tsx @@ -17,7 +17,15 @@ interface Props { } export function NavOption(props: Props): JSX.Element { - const { url, icon, title, subtitle, border, reduced, onClick } = props; + const { + url, + icon, + title, + subtitle, + border = false, + reduced = false, + onClick, + } = props; const router = useRouter(); const isActive = useMemo( () => router.asPath.startsWith(url), @@ -36,7 +44,7 @@ export function NavOption(props: Props): JSX.Element { } placement="right" className="text-left" - disabled={reduced === false} + disabled={!reduced} >
{ @@ -63,7 +71,7 @@ export function NavOption(props: Props): JSX.Element { > {icon && } - {reduced === false && ( + {!reduced && (

{title}

{isDefinedAndNotEmpty(subtitle) && ( diff --git a/src/components/Panels/ContentPanel.tsx b/src/components/Panels/ContentPanel.tsx index 91f30f3..c3fbe8e 100644 --- a/src/components/Panels/ContentPanel.tsx +++ b/src/components/Panels/ContentPanel.tsx @@ -15,10 +15,10 @@ export function ContentPanel(props: Props): JSX.Element { const { width = ContentPanelWidthSizes.Default, children } = props; return ( -
+
{(!mainPanelReduced || !isDesktop) && ( -

Accord’s Library

+

Accord’s Library

)}
{ - setMenuGestures(state); + setMenuGestures(!state); }, [setMenuGestures, state]); return ( diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index 83167b2..07f8e75 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -1,7 +1,7 @@ import { AppStaticProps } from "graphql/getAppStaticProps"; import { getDescription } from "helpers/description"; import { prettySlug } from "helpers/formatters"; -import { getStatusDescription } from "helpers/others"; +import { filterHasAttributes, getStatusDescription } from "helpers/others"; import { PostWithTranslations } from "helpers/types"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { Fragment, useMemo } from "react"; @@ -102,14 +102,12 @@ export function PostPage(props: Props): JSX.Element {

{"Authors"}:

- {post.authors.data.map((author) => ( + {filterHasAttributes(post.authors.data).map((author) => ( - {author.attributes && ( - - )} + ))}
diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx index ff3c330..2e2e095 100644 --- a/src/components/PreviewCard.tsx +++ b/src/components/PreviewCard.tsx @@ -244,7 +244,8 @@ export function PreviewCard(props: Props): JSX.Element { cIf( !keepInfoVisible, `-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute - hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md` + hoverable:drop-shadow-shade-lg notHoverable:opacity-100 + notHoverable:rounded-b-md` ) )} > @@ -302,9 +303,7 @@ interface TranslatedProps languages: AppStaticProps["languages"]; } -export function TranslatedPreviewCard( - props: Immutable -): JSX.Element { +export function TranslatedPreviewCard(props: TranslatedProps): JSX.Element { const { translations = [{ title: props.slug, language: "default" }], slug, diff --git a/src/components/PreviewLine.tsx b/src/components/PreviewLine.tsx index a76e478..5e34e0c 100644 --- a/src/components/PreviewLine.tsx +++ b/src/components/PreviewLine.tsx @@ -89,7 +89,7 @@ interface TranslatedProps } export function TranslatedPreviewLine( - props: Immutable + props: TranslatedProps ): JSX.Element { const { translations = [{ title: props.slug, language: "default" }], diff --git a/src/components/RecorderChip.tsx b/src/components/RecorderChip.tsx index 1da4460..9aadef6 100644 --- a/src/components/RecorderChip.tsx +++ b/src/components/RecorderChip.tsx @@ -2,6 +2,7 @@ import { Chip } from "components/Chip"; import { RecorderChipFragment } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { ImageQuality } from "helpers/img"; +import { filterHasAttributes } from "helpers/others"; import { Fragment } from "react"; import { Img } from "./Img"; @@ -33,13 +34,13 @@ export function RecorderChip(props: Props): JSX.Element { {recorder.languages?.data && recorder.languages.data.length > 0 && (

{langui.languages}:

- {recorder.languages.data.map((language) => ( - - {language.attributes && ( + {filterHasAttributes(recorder.languages.data).map( + (language) => ( + {language.attributes.code.toUpperCase()} - )} - - ))} + + ) + )}
)} {recorder.pronouns && ( diff --git a/src/components/ThumbnailHeader.tsx b/src/components/ThumbnailHeader.tsx index 87dfd97..4d7445c 100644 --- a/src/components/ThumbnailHeader.tsx +++ b/src/components/ThumbnailHeader.tsx @@ -6,6 +6,7 @@ import { GetContentTextQuery, UploadImageFragment } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { prettyinlineTitle, prettySlug, slugify } from "helpers/formatters"; import { getAssetURL, ImageQuality } from "helpers/img"; +import { filterHasAttributes } from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; @@ -89,9 +90,11 @@ export function ThumbnailHeader(props: Props): JSX.Element {

{langui.categories}

- {categories.data.map((category) => ( - {category.attributes?.name} - ))} + {filterHasAttributes(categories.data).map( + (category) => ( + {category.attributes.name} + ) + )}
)} diff --git a/src/components/Wiki/Chronology/ChronologyItemComponent.tsx b/src/components/Wiki/Chronology/ChronologyItemComponent.tsx index c85c4bb..a2af90b 100644 --- a/src/components/Wiki/Chronology/ChronologyItemComponent.tsx +++ b/src/components/Wiki/Chronology/ChronologyItemComponent.tsx @@ -6,7 +6,11 @@ import { GetChronologyItemsQuery, } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; -import { getStatusDescription } from "helpers/others"; +import { + filterDefined, + filterHasAttributes, + getStatusDescription, +} from "helpers/others"; import { Fragment } from "react"; @@ -44,13 +48,16 @@ export function ChronologyItemComponent(props: Props): JSX.Element {

- {props.item.attributes.events?.map((event) => ( - - {event && ( + {props.item.attributes.events && + filterHasAttributes(props.item.attributes.events, [ + "id", + "translations", + ]).map((event) => ( +
- {event.translations?.map((translation, translationIndex) => ( - - {translation && ( + {filterDefined(event.translations).map( + (translation, translationIndex) => ( +
- )} - - ))} + + ) + )}

{event.source?.data ? ( @@ -109,9 +116,8 @@ export function ChronologyItemComponent(props: Props): JSX.Element { )}

- )} -
- ))} +
+ ))}
); diff --git a/src/helpers/libraryItem.ts b/src/helpers/libraryItem.ts index df09011..bada7af 100644 --- a/src/helpers/libraryItem.ts +++ b/src/helpers/libraryItem.ts @@ -3,7 +3,7 @@ import { GetLibraryItemsPreviewQuery } from "graphql/generated"; import { AppStaticProps } from "graphql/getAppStaticProps"; import { prettyinlineTitle, prettyDate } from "./formatters"; import { convertPrice } from "./numbers"; -import { isDefined } from "./others"; +import { isDefined, mapRemoveEmptyValues } from "./others"; import { LibraryItemUserStatus } from "./types"; type Items = NonNullable["data"]; type GroupLibraryItems = Map; @@ -13,67 +13,67 @@ export function getGroups( groupByType: number, items: Items ): GroupLibraryItems { + const groups: GroupLibraryItems = new Map(); + switch (groupByType) { case 0: { - const typeGroup = new Map(); - typeGroup.set("Drakengard 1", []); - typeGroup.set("Drakengard 1.3", []); - typeGroup.set("Drakengard 2", []); - typeGroup.set("Drakengard 3", []); - typeGroup.set("Drakengard 4", []); - typeGroup.set("NieR Gestalt", []); - typeGroup.set("NieR Replicant", []); - typeGroup.set("NieR Replicant ver.1.22474487139...", []); - typeGroup.set("NieR:Automata", []); - typeGroup.set("NieR Re[in]carnation", []); - typeGroup.set("SINoALICE", []); - typeGroup.set("Voice of Cards", []); - typeGroup.set("Final Fantasy XIV", []); - typeGroup.set("Thou Shalt Not Die", []); - typeGroup.set("Bakuken", []); - typeGroup.set("YoRHa", []); - typeGroup.set("YoRHa Boys", []); - typeGroup.set(langui.no_category, []); + const noCategory = langui.no_category ?? "No category"; + groups.set("Drakengard 1", []); + groups.set("Drakengard 1.3", []); + groups.set("Drakengard 2", []); + groups.set("Drakengard 3", []); + groups.set("Drakengard 4", []); + groups.set("NieR Gestalt", []); + groups.set("NieR Replicant", []); + groups.set("NieR Replicant ver.1.22474487139...", []); + groups.set("NieR:Automata", []); + groups.set("NieR Re[in]carnation", []); + groups.set("SINoALICE", []); + groups.set("Voice of Cards", []); + groups.set("Final Fantasy XIV", []); + groups.set("Thou Shalt Not Die", []); + groups.set("Bakuken", []); + groups.set("YoRHa", []); + groups.set("YoRHa Boys", []); + groups.set(noCategory, []); items.map((item) => { if (item.attributes?.categories?.data.length === 0) { - typeGroup.get(langui.no_category)?.push(item); + groups.get(noCategory)?.push(item); } else { item.attributes?.categories?.data.map((category) => { - typeGroup.get(category.attributes?.name)?.push(item); + groups.get(category.attributes?.name ?? noCategory)?.push(item); }); } }); - - return typeGroup; + break; } case 1: { - const group = new Map(); - group.set(langui.audio ?? "Audio", []); - group.set(langui.game ?? "Game", []); - group.set(langui.textual ?? "Textual", []); - group.set(langui.video ?? "Video", []); - group.set(langui.other ?? "Other", []); - group.set(langui.group ?? "Group", []); - group.set(langui.no_type ?? "No type", []); + groups.set(langui.audio ?? "Audio", []); + groups.set(langui.game ?? "Game", []); + groups.set(langui.textual ?? "Textual", []); + groups.set(langui.video ?? "Video", []); + groups.set(langui.other ?? "Other", []); + groups.set(langui.group ?? "Group", []); + groups.set(langui.no_type ?? "No type", []); items.map((item) => { if (item.attributes?.metadata && item.attributes.metadata.length > 0) { switch (item.attributes.metadata[0]?.__typename) { case "ComponentMetadataAudio": - group.get(langui.audio ?? "Audio")?.push(item); + groups.get(langui.audio ?? "Audio")?.push(item); break; case "ComponentMetadataGame": - group.get(langui.game ?? "Game")?.push(item); + groups.get(langui.game ?? "Game")?.push(item); break; case "ComponentMetadataBooks": - group.get(langui.textual ?? "Textual")?.push(item); + groups.get(langui.textual ?? "Textual")?.push(item); break; case "ComponentMetadataVideo": - group.get(langui.video ?? "Video")?.push(item); + groups.get(langui.video ?? "Video")?.push(item); break; case "ComponentMetadataOther": - group.get(langui.other ?? "Other")?.push(item); + groups.get(langui.other ?? "Other")?.push(item); break; case "ComponentMetadataGroup": switch ( @@ -81,19 +81,19 @@ export function getGroups( ?.slug ) { case "audio": - group.get(langui.audio ?? "Audio")?.push(item); + groups.get(langui.audio ?? "Audio")?.push(item); break; case "video": - group.get(langui.video ?? "Video")?.push(item); + groups.get(langui.video ?? "Video")?.push(item); break; case "game": - group.get(langui.game ?? "Game")?.push(item); + groups.get(langui.game ?? "Game")?.push(item); break; case "textual": - group.get(langui.textual ?? "Textual")?.push(item); + groups.get(langui.textual ?? "Textual")?.push(item); break; case "mixed": - group.get(langui.group ?? "Group")?.push(item); + groups.get(langui.group ?? "Group")?.push(item); break; default: { throw new Error( @@ -107,10 +107,10 @@ export function getGroups( } } } else { - group.get(langui.no_type ?? "No type")?.push(item); + groups.get(langui.no_type ?? "No type")?.push(item); } }); - return group; + break; } case 2: { @@ -121,29 +121,28 @@ export function getGroups( years.push(item.attributes.release_date.year); } }); - const group = new Map(); + years.sort((a, b) => a - b); years.map((year) => { - group.set(year.toString(), []); + groups.set(year.toString(), []); }); - group.set(langui.no_year ?? "No year", []); + groups.set(langui.no_year ?? "No year", []); items.map((item) => { if (item.attributes?.release_date?.year) { - group.get(item.attributes.release_date.year.toString())?.push(item); + groups.get(item.attributes.release_date.year.toString())?.push(item); } else { - group.get(langui.no_year ?? "No year")?.push(item); + groups.get(langui.no_year ?? "No year")?.push(item); } }); - - return group; + break; } default: { - const group = new Map(); - group.set("", items); - return group; + groups.set("", items); + break; } } + return mapRemoveEmptyValues(groups); } export function filterItems( @@ -155,7 +154,7 @@ export function filterItems( showSecondaryItems: boolean, filterUserStatus: LibraryItemUserStatus | undefined ): Items { - return [...items].filter((item) => { + return items.filter((item) => { if (!showSubitems && !item.attributes?.root_item) return false; if (showSubitems && isUntangibleGroupItem(item.attributes?.metadata?.[0])) { return false; @@ -212,7 +211,7 @@ export function sortBy( ): Items { switch (orderByType) { case 0: - return [...items].sort((a, b) => { + return items.sort((a, b) => { const titleA = prettyinlineTitle( "", a.attributes?.title, @@ -226,7 +225,7 @@ export function sortBy( return titleA.localeCompare(titleB); }); case 1: - return [...items].sort((a, b) => { + return items.sort((a, b) => { const priceA = a.attributes?.price ? convertPrice(a.attributes.price, currencies[0]) : 99999; @@ -236,7 +235,7 @@ export function sortBy( return priceA - priceB; }); case 2: - return [...items].sort((a, b) => { + return items.sort((a, b) => { const dateA = a.attributes?.release_date ? prettyDate(a.attributes.release_date) : "9999"; diff --git a/src/helpers/others.ts b/src/helpers/others.ts index cdaa15b..64d2dce 100644 --- a/src/helpers/others.ts +++ b/src/helpers/others.ts @@ -4,6 +4,7 @@ import { GetLibraryItemScansQuery, } from "graphql/generated"; import { AppStaticProps } from "../graphql/getAppStaticProps"; +import { SelectiveRequiredNonNullable } from "./types"; type SortContentProps = | NonNullable< @@ -59,11 +60,6 @@ export function getStatusDescription( } } -export function arrayMove(arr: T[], old_index: number, new_index: number) { - arr.splice(new_index, 0, arr.splice(old_index, 1)[0]); - return arr; -} - export function isDefined(t: T): t is NonNullable { return t !== null && t !== undefined; } @@ -78,6 +74,51 @@ export function isDefinedAndNotEmpty( return isDefined(string) && string.length > 0; } -export function filterDefined(t: T[]): NonNullable[] { +export function filterDefined(t: T[] | undefined | null): NonNullable[] { + if (isUndefined(t)) return []; return t.filter((item) => isDefined(item)) as NonNullable[]; } + +export function filterHasAttributes>( + t: T[] | undefined | null, + attributes?: P[] +): SelectiveRequiredNonNullable, P>[] { + if (isUndefined(t)) return []; + return t.filter((item) => { + if (isDefined(item)) { + const attributesToCheck = attributes ?? (Object.keys(item) as P[]); + return attributesToCheck.every((attribute) => isDefined(item[attribute])); + } + return false; + }) as unknown as SelectiveRequiredNonNullable, P>[]; +} + +export function iterateMap( + map: Map, + callbackfn: (key: K, value: V, index: number) => U +): U[] { + const result: U[] = []; + let index = 0; + for (const [key, value] of map.entries()) { + result.push(callbackfn(key, value, index)); + index += 1; + } + return result; +} + +export function mapMoveEntry( + map: Map, + sourceIndex: number, + targetIndex: number +) { + return new Map(arrayMove([...map], sourceIndex, targetIndex)); +} + +function arrayMove(arr: T[], sourceIndex: number, targetIndex: number) { + arr.splice(targetIndex, 0, arr.splice(sourceIndex, 1)[0]); + return arr; +} + +export function mapRemoveEmptyValues(groups: Map): Map { + return new Map([...groups].filter(([_, items]) => items.length > 0)); +} diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 973bc84..68714b9 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -30,9 +30,13 @@ export interface WikiPageWithTranslations translations: NonNullable; } -export type RequiredNonNullable = Required<{ - [P in keyof T]: NonNullable; -}>; +export type RequiredNonNullable = { + [P in keyof T]-?: NonNullable; +}; + +export type SelectiveRequiredNonNullable = Omit & { + [P in K]-?: NonNullable; +}; export enum LibraryItemUserStatus { None = 0, diff --git a/src/hooks/useSmartLanguage.tsx b/src/hooks/useSmartLanguage.tsx index d8ec52e..b3b454c 100644 --- a/src/hooks/useSmartLanguage.tsx +++ b/src/hooks/useSmartLanguage.tsx @@ -1,7 +1,7 @@ import { LanguageSwitcher } from "components/Inputs/LanguageSwitcher"; import { useAppLayout } from "contexts/AppLayoutContext"; import { AppStaticProps } from "graphql/getAppStaticProps"; -import { isDefined } from "helpers/others"; +import { filterDefined, isDefined } from "helpers/others"; import { useRouter } from "next/router"; import { useEffect, useMemo, useState } from "react"; @@ -39,11 +39,9 @@ export function useSmartLanguage( const availableLocales = useMemo(() => { const map = new Map(); - items.map((elem, index) => { - if (isDefined(elem)) { - const result = languageExtractor(elem); - if (isDefined(result)) map.set(result, index); - } + filterDefined(items).map((elem, index) => { + const result = languageExtractor(elem); + if (isDefined(result)) map.set(result, index); }); return map; }, [items, languageExtractor]); diff --git a/src/pages/archives/videos/c/[uid].tsx b/src/pages/archives/videos/c/[uid].tsx index 744853b..15cb249 100644 --- a/src/pages/archives/videos/c/[uid].tsx +++ b/src/pages/archives/videos/c/[uid].tsx @@ -24,7 +24,7 @@ import { Fragment, useState } from "react"; import { Icon } from "components/Ico"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { WithLabel } from "components/Inputs/WithLabel"; -import { isDefined } from "helpers/others"; +import { filterHasAttributes, isDefined } from "helpers/others"; interface Props extends AppStaticProps { channel: NonNullable< @@ -74,27 +74,25 @@ export default function Channel(props: Props): JSX.Element { className="grid items-start gap-8 border-b-[3px] border-dotted pb-12 last-of-type:border-0 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2" > - {channel?.videos?.data.map((video) => ( + {filterHasAttributes(channel?.videos?.data).map((video) => ( - {video.attributes && ( - - )} + ))}
@@ -137,13 +135,12 @@ export async function getStaticPaths( const channels = await sdk.getVideoChannelsSlugs(); const paths: GetStaticPathsResult["paths"] = []; if (channels.videoChannels?.data) - channels.videoChannels.data.map((channel) => { + filterHasAttributes(channels.videoChannels.data).map((channel) => { context.locales?.map((local) => { - if (channel.attributes) - paths.push({ - params: { uid: channel.attributes.uid }, - locale: local, - }); + paths.push({ + params: { uid: channel.attributes.uid }, + locale: local, + }); }); }); return { diff --git a/src/pages/archives/videos/index.tsx b/src/pages/archives/videos/index.tsx index 5aa39fb..f3b9414 100644 --- a/src/pages/archives/videos/index.tsx +++ b/src/pages/archives/videos/index.tsx @@ -18,6 +18,7 @@ import { GetVideosPreviewQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyDate } from "helpers/formatters"; +import { filterHasAttributes } from "helpers/others"; import { getVideoThumbnailURL } from "helpers/videos"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { GetStaticPropsContext } from "next"; @@ -95,27 +96,25 @@ export default function Videos(props: Props): JSX.Element { desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:grid-cols-2 thin:grid-cols-1" > - {paginatedVideos[page].map((video) => ( + {filterHasAttributes(paginatedVideos[page]).map((video) => ( - {video.attributes && ( - - )} + ))}
diff --git a/src/pages/archives/videos/v/[uid].tsx b/src/pages/archives/videos/v/[uid].tsx index 813e1a7..52ea437 100644 --- a/src/pages/archives/videos/v/[uid].tsx +++ b/src/pages/archives/videos/v/[uid].tsx @@ -18,7 +18,7 @@ import { GetVideoQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyDate, prettyShortenNumber } from "helpers/formatters"; -import { isDefined } from "helpers/others"; +import { filterHasAttributes, isDefined } from "helpers/others"; import { getVideoFile } from "helpers/videos"; import { useMediaMobile } from "hooks/useMediaQuery"; import { @@ -213,10 +213,9 @@ export async function getStaticPaths( const videos = await sdk.getVideosSlugs(); const paths: GetStaticPathsResult["paths"] = []; if (videos.videos?.data) - videos.videos.data.map((video) => { + filterHasAttributes(videos.videos.data).map((video) => { context.locales?.map((local) => { - if (video.attributes) - paths.push({ params: { uid: video.attributes.uid }, locale: local }); + paths.push({ params: { uid: video.attributes.uid }, locale: local }); }); }); return { diff --git a/src/pages/contents/[slug]/index.tsx b/src/pages/contents/[slug]/index.tsx index 00f4061..407e957 100644 --- a/src/pages/contents/[slug]/index.tsx +++ b/src/pages/contents/[slug]/index.tsx @@ -26,7 +26,11 @@ import { prettySlug, } from "helpers/formatters"; import { isUntangibleGroupItem } from "helpers/libraryItem"; -import { getStatusDescription } from "helpers/others"; +import { + filterHasAttributes, + getStatusDescription, + isDefinedAndNotEmpty, +} from "helpers/others"; import { ContentWithTranslations } from "helpers/types"; import { useMediaMobile } from "hooks/useMediaQuery"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; @@ -82,31 +86,29 @@ export default function Content(props: Props): JSX.Element { horizontalLine /> - {selectedTranslation?.text_set && ( + {selectedTranslation?.text_set?.source_language?.data?.attributes + ?.code !== undefined && (

- {selectedTranslation.text_set.source_language?.data?.attributes - ?.code === selectedTranslation.language?.data?.attributes?.code + {selectedTranslation.text_set.source_language.data.attributes + .code === selectedTranslation.language?.data?.attributes?.code ? langui.transcript_notice : langui.translation_notice}

- {selectedTranslation.text_set.source_language?.data?.attributes - ?.code && - selectedTranslation.text_set.source_language.data.attributes - .code !== - selectedTranslation.language?.data?.attributes?.code && ( -
-

{langui.source_language}:

- - {prettyLanguage( - selectedTranslation.text_set.source_language.data.attributes - .code, - languages - )} - -
- )} + {selectedTranslation.text_set.source_language.data.attributes.code !== + selectedTranslation.language?.data?.attributes?.code && ( +
+

{langui.source_language}:

+ + {prettyLanguage( + selectedTranslation.text_set.source_language.data.attributes + .code, + languages + )} + +
+ )}

{langui.status}:

@@ -127,18 +129,16 @@ export default function Content(props: Props): JSX.Element {

{langui.transcribers}:

- {selectedTranslation.text_set.transcribers.data.map( - (recorder) => ( - - {recorder.attributes && ( - - )} - - ) - )} + {filterHasAttributes( + selectedTranslation.text_set.transcribers.data + ).map((recorder) => ( + + + + ))}
)} @@ -148,18 +148,16 @@ export default function Content(props: Props): JSX.Element {

{langui.translators}:

- {selectedTranslation.text_set.translators.data.map( - (recorder) => ( - - {recorder.attributes && ( - - )} - - ) - )} + {filterHasAttributes( + selectedTranslation.text_set.translators.data + ).map((recorder) => ( + + + + ))}
)} @@ -169,23 +167,21 @@ export default function Content(props: Props): JSX.Element {

{langui.proofreaders}:

- {selectedTranslation.text_set.proofreaders.data.map( - (recorder) => ( - - {recorder.attributes && ( - - )} - - ) - )} + {filterHasAttributes( + selectedTranslation.text_set.proofreaders.data + ).map((recorder) => ( + + + + ))}
)} - {selectedTranslation.text_set.notes && ( + {isDefinedAndNotEmpty(selectedTranslation.text_set.notes) && (

{"Notes"}:

@@ -450,13 +446,12 @@ export async function getStaticPaths( const sdk = getReadySdk(); const contents = await sdk.getContentsSlugs(); const paths: GetStaticPathsResult["paths"] = []; - contents.contents?.data.map((item) => { + filterHasAttributes(contents.contents?.data).map((item) => { context.locales?.map((local) => { - if (item.attributes) - paths.push({ - params: { slug: item.attributes.slug }, - locale: local, - }); + paths.push({ + params: { slug: item.attributes.slug }, + locale: local, + }); }); }); return { diff --git a/src/pages/contents/index.tsx b/src/pages/contents/index.tsx index 27a42a6..4f3fcfe 100644 --- a/src/pages/contents/index.tsx +++ b/src/pages/contents/index.tsx @@ -21,6 +21,12 @@ import { WithLabel } from "components/Inputs/WithLabel"; import { Button } from "components/Inputs/Button"; import { TextInput } from "components/Inputs/TextInput"; import { useMediaHoverable } from "hooks/useMediaQuery"; +import { + filterHasAttributes, + iterateMap, + mapRemoveEmptyValues, +} from "helpers/others"; +import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; interface Props extends AppStaticProps { contents: NonNullable["data"]; @@ -128,8 +134,18 @@ export default function Contents(props: Props): JSX.Element { ); const contentPanel = ( - {[...groups].map( - ([name, items], index) => + {/* TODO: Add to langui */} + {groups.size === 0 && ( + + )} + {iterateMap( + groups, + (name, items, index) => items.length > 0 && ( {name && ( @@ -140,7 +156,10 @@ export default function Contents(props: Props): JSX.Element { {name} {`${items.reduce((currentSum, item) => { if (effectiveCombineRelatedContent) { - if (item.attributes?.group?.data?.attributes?.combine) { + if ( + item.attributes?.group?.data?.attributes?.combine === + true + ) { return ( currentSum + (item.attributes.group.data.attributes.contents?.data @@ -161,50 +180,49 @@ export default function Contents(props: Props): JSX.Element { className="grid grid-cols-2 items-end gap-8 desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4" > - {items.map((item) => ( + {filterHasAttributes(items).map((item) => ( - {item.attributes && ( - ({ - pre_title: translation?.pre_title, - title: translation?.title, - subtitle: translation?.subtitle, - language: - translation?.language?.data?.attributes?.code, - }) - )} - slug={item.attributes.slug} - languages={languages} - thumbnail={item.attributes.thumbnail?.data?.attributes} - thumbnailAspectRatio="3/2" - thumbnailForceAspectRatio - stackNumber={ - effectiveCombineRelatedContent && - item.attributes.group?.data?.attributes?.combine - ? item.attributes.group.data.attributes.contents - ?.data.length - : 0 - } - topChips={ - item.attributes.type?.data?.attributes - ? [ - item.attributes.type.data.attributes.titles?.[0] - ? item.attributes.type.data.attributes - .titles[0]?.title - : prettySlug( - item.attributes.type.data.attributes.slug - ), - ] - : undefined - } - bottomChips={item.attributes.categories?.data.map( - (category) => category.attributes?.short ?? "" - )} - keepInfoVisible={keepInfoVisible} - /> - )} + ({ + pre_title: translation?.pre_title, + title: translation?.title, + subtitle: translation?.subtitle, + language: + translation?.language?.data?.attributes?.code, + }) + )} + slug={item.attributes.slug} + languages={languages} + thumbnail={item.attributes.thumbnail?.data?.attributes} + thumbnailAspectRatio="3/2" + thumbnailForceAspectRatio + stackNumber={ + effectiveCombineRelatedContent && + item.attributes.group?.data?.attributes?.combine === + true + ? item.attributes.group.data.attributes.contents?.data + .length + : 0 + } + topChips={ + item.attributes.type?.data?.attributes + ? [ + item.attributes.type.data.attributes.titles?.[0] + ? item.attributes.type.data.attributes.titles[0] + ?.title + : prettySlug( + item.attributes.type.data.attributes.slug + ), + ] + : undefined + } + bottomChips={item.attributes.categories?.data.map( + (category) => category.attributes?.short ?? "" + )} + keepInfoVisible={keepInfoVisible} + /> ))}
@@ -252,71 +270,72 @@ function getGroups( groupByType: number, items: Props["contents"] ): GroupContentItems { + const groups: GroupContentItems = new Map(); + switch (groupByType) { case 0: { - const group = new Map(); - group.set("Drakengard 1", []); - group.set("Drakengard 1.3", []); - group.set("Drakengard 2", []); - group.set("Drakengard 3", []); - group.set("Drakengard 4", []); - group.set("NieR Gestalt", []); - group.set("NieR Replicant", []); - group.set("NieR Replicant ver.1.22474487139...", []); - group.set("NieR:Automata", []); - group.set("NieR Re[in]carnation", []); - group.set("SINoALICE", []); - group.set("Voice of Cards", []); - group.set("Final Fantasy XIV", []); - group.set("Thou Shalt Not Die", []); - group.set("Bakuken", []); - group.set("YoRHa", []); - group.set("YoRHa Boys", []); - group.set(langui.no_category, []); + const noCategory = langui.no_category ?? "No category"; + groups.set("Drakengard 1", []); + groups.set("Drakengard 1.3", []); + groups.set("Drakengard 2", []); + groups.set("Drakengard 3", []); + groups.set("Drakengard 4", []); + groups.set("NieR Gestalt", []); + groups.set("NieR Replicant", []); + groups.set("NieR Replicant ver.1.22474487139...", []); + groups.set("NieR:Automata", []); + groups.set("NieR Re[in]carnation", []); + groups.set("SINoALICE", []); + groups.set("Voice of Cards", []); + groups.set("Final Fantasy XIV", []); + groups.set("Thou Shalt Not Die", []); + groups.set("Bakuken", []); + groups.set("YoRHa", []); + groups.set("YoRHa Boys", []); + groups.set(noCategory, []); items.map((item) => { if (item.attributes?.categories?.data.length === 0) { - group.get(langui.no_category)?.push(item); + groups.get(noCategory)?.push(item); } else { item.attributes?.categories?.data.map((category) => { - group.get(category.attributes?.name)?.push(item); + groups.get(category.attributes?.name ?? noCategory)?.push(item); }); } }); - return group; + break; } case 1: { - const group = new Map(); items.map((item) => { + const noType = langui.no_type ?? "No type"; const type = item.attributes?.type?.data?.attributes?.titles?.[0]?.title ?? item.attributes?.type?.data?.attributes?.slug ? prettySlug(item.attributes.type.data.attributes.slug) : langui.no_type; - if (!group.has(type)) group.set(type, []); - group.get(type)?.push(item); + if (!groups.has(type ?? noType)) groups.set(type ?? noType, []); + groups.get(type ?? noType)?.push(item); }); - return group; + break; } default: { - const group: GroupContentItems = new Map(); - group.set("", items); - return group; + groups.set("", items); } } + return mapRemoveEmptyValues(groups); } function filterContents( - contents: Immutable, + contents: Props["contents"], combineRelatedContent: boolean, searchName: string -): Immutable { +): Props["contents"] { return contents.filter((content) => { if ( combineRelatedContent && - content.attributes?.group?.data?.attributes?.combine && + content.attributes?.group?.data?.attributes?.combine === true && content.attributes.group.data.attributes.contents?.data[0].id !== content.id ) { diff --git a/src/pages/dev/checkup/contents.tsx b/src/pages/dev/checkup/contents.tsx index 6036af6..64ffc9f 100644 --- a/src/pages/dev/checkup/contents.tsx +++ b/src/pages/dev/checkup/contents.tsx @@ -9,6 +9,7 @@ import { ToolTip } from "components/ToolTip"; import { DevGetContentsQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; +import { filterDefined, filterHasAttributes } from "helpers/others"; import { GetStaticPropsContext } from "next"; @@ -116,321 +117,319 @@ function testingContent(contents: Props["contents"]): Report { lines: [], }; - contents.contents?.data.map((content) => { - if (content.attributes) { - 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).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[] = []; - content.attributes.translations?.map((translation, titleIndex) => { - if (translation && content.attributes) { - 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 { + 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: "No Language", + name: "Duplicate Language", type: "Error", - severity: "Very High", + severity: "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) { - 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, - * }); - * } - * } - *} - */ + titleLanguages.push(translation.language.data.id); } - + } else { report.lines.push({ - subitems: [content.attributes.slug], - name: "No Sets", - type: "Missing", - severity: "Medium", - description: "The Content has no Sets.", + 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) { + 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 Sets", + type: "Missing", + severity: "Medium", + description: "The Content has no Sets.", + recommandation: "", + backendUrl: backendUrl, + frontendUrl: frontendUrl, + }); + } + ); } }); return report; diff --git a/src/pages/library/[slug]/index.tsx b/src/pages/library/[slug]/index.tsx index d40dfbe..6289977 100644 --- a/src/pages/library/[slug]/index.tsx +++ b/src/pages/library/[slug]/index.tsx @@ -35,7 +35,12 @@ import { } from "helpers/formatters"; import { getAssetURL, ImageQuality } from "helpers/img"; import { convertMmToInch } from "helpers/numbers"; -import { isDefined, sortContent } from "helpers/others"; +import { + filterHasAttributes, + isDefined, + isDefinedAndNotEmpty, + sortContent, +} from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; import { AnchorIds, useScrollTopOnChange } from "hooks/useScrollTopOnChange"; @@ -172,7 +177,9 @@ export default function LibrarySlug(props: Props): JSX.Element { )}

{item?.title}

- {item?.subtitle &&

{item.subtitle}

} + {item && isDefinedAndNotEmpty(item.subtitle) && ( +

{item.subtitle}

+ )}

{langui.available_at}

- {item.urls - .filter((url) => url) - .map((url, index) => ( - - {url?.url && ( -
) : (

{langui.item_not_available}

@@ -225,26 +228,19 @@ export default function LibrarySlug(props: Props): JSX.Element { className="grid w-full grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] items-end gap-8" > - {item.gallery.data.map((galleryItem, index) => ( - - {galleryItem.attributes && ( + {filterHasAttributes(item.gallery.data).map( + (galleryItem, index) => ( +
{ - if (item.gallery?.data) { - const images: string[] = []; - item.gallery.data.map((image) => { - if (image.attributes) - images.push( - getAssetURL( - image.attributes.url, - ImageQuality.Large - ) - ); - }); - openLightBox(images, index); - } + const images: string[] = filterHasAttributes( + item.gallery?.data + ).map((image) => + getAssetURL(image.attributes.url, ImageQuality.Large) + ); + openLightBox(images, index); }} >
- )} -
- ))} +
+ ) + )}
)} @@ -437,46 +433,44 @@ export default function LibrarySlug(props: Props): JSX.Element { className="grid w-full grid-cols-[repeat(auto-fill,minmax(15rem,1fr))] items-end gap-8 mobile:grid-cols-2 thin:grid-cols-1" > - {item.subitems.data.map((subitem) => ( + {filterHasAttributes(item.subitems.data).map((subitem) => ( - {subitem.attributes && subitem.id && ( - 0 && - subitem.attributes.metadata[0] - ? [prettyItemSubType(subitem.attributes.metadata[0])] - : [] - } - bottomChips={subitem.attributes.categories?.data.map( - (category) => category.attributes?.short ?? "" - )} - metadata={{ - currencies: currencies, - release_date: subitem.attributes.release_date, - price: subitem.attributes.price, - position: "Bottom", - }} - infoAppend={ - - } - /> - )} + 0 && + subitem.attributes.metadata[0] + ? [prettyItemSubType(subitem.attributes.metadata[0])] + : [] + } + bottomChips={subitem.attributes.categories?.data.map( + (category) => category.attributes?.short ?? "" + )} + metadata={{ + currencies: currencies, + release_date: subitem.attributes.release_date, + price: subitem.attributes.price, + position: "Bottom", + }} + infoAppend={ + + } + /> ))}
@@ -548,13 +542,12 @@ export async function getStaticPaths( const sdk = getReadySdk(); const libraryItems = await sdk.getLibraryItemsSlugs(); const paths: GetStaticPathsResult["paths"] = []; - if (libraryItems.libraryItems) { - libraryItems.libraryItems.data.map((item) => { - context.locales?.map((local) => { - paths.push({ params: { slug: item.attributes?.slug }, locale: local }); - }); - }); - } + filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { + context.locales?.map((local) => + paths.push({ params: { slug: item.attributes?.slug }, locale: local }) + ); + }); + return { paths, fallback: "blocking", diff --git a/src/pages/library/[slug]/scans.tsx b/src/pages/library/[slug]/scans.tsx index 72ad4ef..5426039 100644 --- a/src/pages/library/[slug]/scans.tsx +++ b/src/pages/library/[slug]/scans.tsx @@ -15,7 +15,7 @@ import { GetLibraryItemScansQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettyinlineTitle, prettySlug } from "helpers/formatters"; -import { isDefined, sortContent } from "helpers/others"; +import { filterHasAttributes, isDefined, sortContent } from "helpers/others"; import { useLightBox } from "hooks/useLightBox"; import { @@ -146,14 +146,12 @@ export async function getStaticPaths( const sdk = getReadySdk(); const libraryItems = await sdk.getLibraryItemsSlugs({}); const paths: GetStaticPathsResult["paths"] = []; - if (libraryItems.libraryItems) { - libraryItems.libraryItems.data.map((item) => { - context.locales?.map((local) => { - if (item.attributes) - paths.push({ params: { slug: item.attributes.slug }, locale: local }); - }); - }); - } + filterHasAttributes(libraryItems.libraryItems?.data).map((item) => { + context.locales?.map((local) => + paths.push({ params: { slug: item.attributes.slug }, locale: local }) + ); + }); + return { paths, fallback: "blocking", diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx index 6878aea..71e4326 100644 --- a/src/pages/library/index.tsx +++ b/src/pages/library/index.tsx @@ -31,7 +31,13 @@ import { import { PreviewCard } from "components/PreviewCard"; import { useMediaHoverable } from "hooks/useMediaQuery"; import { ButtonGroup } from "components/Inputs/ButtonGroup"; -import { isDefinedAndNotEmpty, isUndefined } from "helpers/others"; +import { + filterHasAttributes, + isDefinedAndNotEmpty, + isUndefined, + iterateMap, +} from "helpers/others"; +import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; interface Props extends AppStaticProps { items: NonNullable["data"]; @@ -234,73 +240,74 @@ export default function Library(props: Props): JSX.Element { ); const contentPanel = ( - {[...groups].map(([name, items]) => ( + {/* TODO: Add to langui */} + {groups.size === 0 && ( + + )} + {iterateMap(groups, (name, items) => ( - {items.length > 0 && ( - <> - {name && ( -

- {name} - {`${items.length} ${ - items.length <= 1 - ? langui.result?.toLowerCase() ?? "result" - : langui.results?.toLowerCase() ?? "results" - }`} -

- )} -
+ + )} +
- {items.map((item) => ( - - {isDefinedAndNotEmpty(item.id) && item.attributes && ( - 0 && - item.attributes.metadata[0] - ? [prettyItemSubType(item.attributes.metadata[0])] - : [] - } - bottomChips={item.attributes.categories?.data.map( - (category) => category.attributes?.short ?? "" - )} - metadata={{ - currencies: currencies, - release_date: item.attributes.release_date, - price: item.attributes.price, - position: "Bottom", - }} - infoAppend={ - - } - /> - )} - - ))} -
- - )} + > + {filterHasAttributes(items).map((item) => ( + + 0 && + item.attributes.metadata[0] + ? [prettyItemSubType(item.attributes.metadata[0])] + : [] + } + bottomChips={item.attributes.categories?.data.map( + (category) => category.attributes?.short ?? "" + )} + metadata={{ + currencies: currencies, + release_date: item.attributes.release_date, + price: item.attributes.price, + position: "Bottom", + }} + infoAppend={ + + } + /> + + ))} +
))}
diff --git a/src/pages/news/[slug].tsx b/src/pages/news/[slug].tsx index 5d5f3cf..8b0f21b 100644 --- a/src/pages/news/[slug].tsx +++ b/src/pages/news/[slug].tsx @@ -5,7 +5,7 @@ import { PostStaticProps, } from "graphql/getPostStaticProps"; import { getReadySdk } from "graphql/sdk"; -import { isDefined } from "helpers/others"; +import { filterHasAttributes, isDefined } from "helpers/others"; import { GetStaticPathsContext, @@ -48,13 +48,12 @@ export async function getStaticPaths( const sdk = getReadySdk(); const posts = await sdk.getPostsSlugs(); const paths: GetStaticPathsResult["paths"] = []; - if (posts.posts) - posts.posts.data.map((item) => { - context.locales?.map((local) => { - if (item.attributes) - paths.push({ params: { slug: item.attributes.slug }, locale: local }); - }); - }); + + filterHasAttributes(posts.posts?.data).map((item) => { + context.locales?.map((local) => + paths.push({ params: { slug: item.attributes.slug }, locale: local }) + ); + }); return { paths, fallback: "blocking", diff --git a/src/pages/news/index.tsx b/src/pages/news/index.tsx index 5f5d741..d745795 100644 --- a/src/pages/news/index.tsx +++ b/src/pages/news/index.tsx @@ -19,6 +19,7 @@ import { WithLabel } from "components/Inputs/WithLabel"; import { TextInput } from "components/Inputs/TextInput"; import { Button } from "components/Inputs/Button"; import { useMediaHoverable } from "hooks/useMediaQuery"; +import { filterHasAttributes } from "helpers/others"; interface Props extends AppStaticProps { posts: NonNullable["data"]; @@ -87,29 +88,27 @@ export default function News(props: Props): JSX.Element { className="grid grid-cols-1 items-end gap-8 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]" > - {filteredItems.map((post) => ( + {filterHasAttributes(filteredItems).map((post) => ( - {post.attributes && ( - category.attributes?.short ?? "" - )} - keepInfoVisible={keepInfoVisible} - metadata={{ - release_date: post.attributes.date, - position: "Top", - }} - /> - )} + category.attributes?.short ?? "" + )} + keepInfoVisible={keepInfoVisible} + metadata={{ + release_date: post.attributes.date, + position: "Top", + }} + /> ))}
@@ -145,19 +144,17 @@ export async function getStaticProps( } function sortPosts(posts: Props["posts"]): Props["posts"] { - const sortedPosts = [...posts]; - sortedPosts + return posts .sort((a, b) => { const dateA = a.attributes?.date ? prettyDate(a.attributes.date) : "9999"; const dateB = b.attributes?.date ? prettyDate(b.attributes.date) : "9999"; return dateA.localeCompare(dateB); }) .reverse(); - return sortedPosts; } function filterItems(posts: Props["posts"], searchName: string) { - return [...posts].filter((post) => { + return posts.filter((post) => { if (searchName.length > 1) { if ( post.attributes?.translations?.[0]?.title diff --git a/src/pages/wiki/[slug]/index.tsx b/src/pages/wiki/[slug]/index.tsx index 32aec1b..7d70046 100644 --- a/src/pages/wiki/[slug]/index.tsx +++ b/src/pages/wiki/[slug]/index.tsx @@ -14,7 +14,11 @@ import { SubPanel } from "components/Panels/SubPanel"; import DefinitionCard from "components/Wiki/DefinitionCard"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; -import { isDefined, isDefinedAndNotEmpty } from "helpers/others"; +import { + filterHasAttributes, + isDefined, + isDefinedAndNotEmpty, +} from "helpers/others"; import { WikiPageWithTranslations } from "helpers/types"; import { useSmartLanguage } from "hooks/useSmartLanguage"; import { @@ -89,20 +93,24 @@ export default function WikiPage(props: Props): JSX.Element {
)} - {page.definitions?.map((definition, index) => ( - ({ - language: translation?.language?.data?.attributes?.code, - definition: translation?.definition, - status: translation?.status, - }))} - index={index + 1} - languages={languages} - langui={langui} - /> - ))} + {filterHasAttributes(page.definitions, ["translations"]).map( + (definition, index) => ( + ({ + language: translation.language.data?.attributes?.code, + definition: translation.definition, + status: translation.status, + }) + )} + index={index + 1} + languages={languages} + langui={langui} + /> + ) + )}
)} @@ -147,14 +155,13 @@ export async function getStaticPaths( const sdk = getReadySdk(); const contents = await sdk.getWikiPagesSlugs(); const paths: GetStaticPathsResult["paths"] = []; - contents.wikiPages?.data.map((wikiPage) => { - context.locales?.map((local) => { - if (wikiPage.attributes) - paths.push({ - params: { slug: wikiPage.attributes.slug }, - locale: local, - }); - }); + filterHasAttributes(contents.wikiPages?.data).map((wikiPage) => { + context.locales?.map((local) => + paths.push({ + params: { slug: wikiPage.attributes.slug }, + locale: local, + }) + ); }); return { paths, diff --git a/src/pages/wiki/chronology.tsx b/src/pages/wiki/chronology.tsx index 829d714..66f8d16 100644 --- a/src/pages/wiki/chronology.tsx +++ b/src/pages/wiki/chronology.tsx @@ -12,7 +12,7 @@ import { GetChronologyItemsQuery, GetErasQuery } from "graphql/generated"; import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps"; import { getReadySdk } from "graphql/sdk"; import { prettySlug } from "helpers/formatters"; -import { isDefined } from "helpers/others"; +import { filterHasAttributes, isDefined } from "helpers/others"; import { GetStaticPropsContext } from "next"; import { Fragment } from "react"; @@ -70,22 +70,20 @@ export default function Chronology(props: Props): JSX.Element { horizontalLine /> - {chronologyEras.map((era) => ( + {filterHasAttributes(chronologyEras).map((era) => ( - {era.attributes && ( - 0 && - era.attributes.title[0] - ? era.attributes.title[0].title - : prettySlug(era.attributes.slug) - } - subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`} - border - /> - )} + 0 && + era.attributes.title[0] + ? era.attributes.title[0].title + : prettySlug(era.attributes.slug) + } + subtitle={`${era.attributes.starting_year} → ${era.attributes.ending_year}`} + border + /> ))} diff --git a/src/pages/wiki/index.tsx b/src/pages/wiki/index.tsx index 1826b9a..6b2ff7a 100644 --- a/src/pages/wiki/index.tsx +++ b/src/pages/wiki/index.tsx @@ -20,6 +20,8 @@ import { Switch } from "components/Inputs/Switch"; import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; import { useMediaHoverable } from "hooks/useMediaQuery"; +import { filterHasAttributes } from "helpers/others"; +import { ContentPlaceholder } from "components/PanelComponents/ContentPlaceholder"; interface Props extends AppStaticProps { pages: NonNullable["data"]; @@ -93,30 +95,37 @@ export default function Wiki(props: Props): JSX.Element { className="grid grid-cols-2 items-end gap-8 desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4" > - {filteredPages.map((page) => ( + {/* TODO: Add to langui */} + {filteredPages.length === 0 && ( + + )} + {filterHasAttributes(filteredPages).map((page) => ( - {page.attributes && ( - ({ - title: translation?.title, - 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 ?? "" - )} - /> - )} + ({ + title: translation?.title, + 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 ?? "" + )} + /> ))}
@@ -150,17 +159,15 @@ export async function getStaticProps( } function sortPages(pages: Props["pages"]): Props["pages"] { - const sortedPages = [...pages]; - sortedPages.sort((a, b) => { + return pages.sort((a, b) => { const slugA = a.attributes?.slug ?? ""; const slugB = b.attributes?.slug ?? ""; return slugA.localeCompare(slugB); }); - return sortedPages; } function filterPages(posts: Props["pages"], searchName: string) { - return [...posts].filter((post) => { + return posts.filter((post) => { if (searchName.length > 1) { if ( post.attributes?.translations?.[0]?.title diff --git a/src/tailwind.css b/src/tailwind.css index 0239426..3cda969 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -31,7 +31,7 @@ mark { /* SCROLLBARS STYLING */ * { - @apply [scrollbar-color:theme(colors.dark)_transparent] [scrollbar-width:thin]; + @apply [scrollbar-color:theme(colors.dark/1)_transparent] [scrollbar-width:thin]; } *::-webkit-scrollbar {