From 57399a60dd9171c45a0e78b62db2137c6a4fd8b7 Mon Sep 17 00:00:00 2001 From: DrMint <thomas.barillot@etu.u-bordeaux.fr> Date: Sat, 19 Feb 2022 02:14:16 +0100 Subject: [PATCH] Added language selector + better use of components --- src/components/AppLayout.tsx | 62 +++++- src/components/Button.tsx | 12 +- src/components/Chip.tsx | 6 +- src/components/InsetBox.tsx | 16 ++ src/components/PanelComponents/NavOption.tsx | 2 +- src/components/Panels/MainPanel.tsx | 3 +- src/graphql/operation.graphql | 222 +++++++++---------- src/graphql/operations-types.ts | 1 + src/pages/library/items/[slug].tsx | 14 +- src/pages/wiki/chronology.tsx | 72 ++++-- src/queries/helpers.ts | 23 +- src/tailwind.css | 6 +- tailwind.config.js | 2 +- 13 files changed, 279 insertions(+), 162 deletions(-) create mode 100644 src/components/InsetBox.tsx diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index b11b12f..327c857 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -3,6 +3,9 @@ import MainPanel from "./Panels/MainPanel"; import { useState } from "react"; import Head from "next/head"; import { useSwipeable } from "react-swipeable"; +import { useRouter } from "next/router"; +import Button from "components/Button"; +import { prettyLanguage } from "queries/helpers"; type AppLayoutProps = { subPanel?: React.ReactNode; @@ -14,9 +17,11 @@ type AppLayoutProps = { export default function AppLayout(props: AppLayoutProps): JSX.Element { const titlePrefix = "Accord’s Library"; + const router = useRouter(); const [mainPanelOpen, setMainPanelOpen] = useState(false); - const [subPanelOpen, setsubPanelOpen] = useState(false); + const [subPanelOpen, setSubPanelOpen] = useState(false); + const [languagePanelOpen, setLanguagePanelOpen] = useState(false); const sensibilitySwipe = 1.1; const handlers = useSwipeable({ @@ -25,13 +30,13 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element { if (mainPanelOpen) { setMainPanelOpen(false); } else if (props.subPanel && props.contentPanel) { - setsubPanelOpen(true); + setSubPanelOpen(true); } }, onSwipedRight: (SwipeEventData) => { if (SwipeEventData.velocity < sensibilitySwipe) return; if (subPanelOpen) { - setsubPanelOpen(false); + setSubPanelOpen(false); } else { setMainPanelOpen(true); } @@ -74,7 +79,7 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element { <p className="text-2xl font-black font-headers">{props.title}</p> <span className="material-icons mt-[.1em] cursor-pointer" - onClick={() => setsubPanelOpen(true)} + onClick={() => setSubPanelOpen(true)} > {props.subPanel && !turnSubIntoContent ? props.subPanelIcon @@ -110,12 +115,12 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element { ${turnSubIntoContent ? "z-10" : ""} ${ mainPanelOpen || subPanelOpen - ? " opacity-50" - : "opacity-0 translate-x-full" + ? "opacity-50" + : "opacity-0 pointer-events-none touch-none" }`} onClick={() => { setMainPanelOpen(false); - setsubPanelOpen(false); + setSubPanelOpen(false); }} ></div> @@ -142,7 +147,48 @@ export default function AppLayout(props: AppLayoutProps): JSX.Element { className={`${mainPanelClass} border-r-[1px] border-black border-dotted top-0 bottom-0 left-0 right-12 overflow-y-scroll webkit-scrollbar:w-0 [scrollbar-width:none] transition-transform duration-300 z-20 bg-light bg-paper bg-blend-multiply bg-local bg-[length:10cm] ${mainPanelOpen ? "" : "mobile:-translate-x-full"}`} > - <MainPanel langui={props.langui} /> + <MainPanel + langui={props.langui} + setLanguagePanelOpen={setLanguagePanelOpen} + /> + </div> + + {/* Language selection background */} + <div + className={`fixed bg-dark inset-0 transition-all duration-500 z-20 grid place-content-center ${ + languagePanelOpen + ? "bg-opacity-50" + : "bg-opacity-0 pointer-events-none touch-none" + }`} + onClick={() => { + setLanguagePanelOpen(false); + }} + > + <div + className={`p-10 bg-light rounded-lg shadow-2xl shadow-dark grid gap-4 place-items-center transition-transform ${ + languagePanelOpen ? "scale-100" : "scale-0" + }`} + > + <h2 className="text-2xl">Select a language</h2> + <div className="flex flex-wrap flex-row gap-2"> + {router.locales?.sort().map((locale) => ( + <> + {locale !== "xx" ? ( + <Button + key={locale} + active={locale === router.locale} + href={router.asPath} + locale={locale} + > + {prettyLanguage(locale)} + </Button> + ) : ( + "" + )} + </> + ))} + </div> + </div> </div> </div> ); diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 772dd0e..9f87826 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -4,19 +4,27 @@ type ButtonProps = { className?: string; href?: string; children: React.ReactChild | React.ReactChild[]; + active?: boolean; + locale?: string; }; export default function Button(props: ButtonProps): JSX.Element { const button = ( <div - className={`grid place-content-center place-items-center border-[1px] border-dark text-dark rounded-full cursor-pointer px-4 pt-[0.4rem] pb-[0.5rem] transition-all hover:text-light hover:bg-dark hover:drop-shadow-dark-lg active:bg-black active:drop-shadow-black-lg active:border-black ${props.className}`} + className={`grid place-content-center place-items-center border-[1px] border-dark text-dark rounded-full px-4 pt-[0.4rem] pb-[0.5rem] transition-all ${ + props.className + } ${ + props.active + ? "text-light bg-black drop-shadow-black-lg !border-black cursor-not-allowed" + : "cursor-pointer hover:text-light hover:bg-dark hover:drop-shadow-dark-lg active:bg-black active:drop-shadow-black-lg active:border-black" + }`} > {props.children} </div> ); const result = props.href ? ( - <Link href={props.href} passHref> + <Link href={props.href} locale={props.locale} passHref> {button} </Link> ) : ( diff --git a/src/components/Chip.tsx b/src/components/Chip.tsx index 449ee26..20b4c6b 100644 --- a/src/components/Chip.tsx +++ b/src/components/Chip.tsx @@ -6,11 +6,7 @@ type ChipProps = { export default function Chip(props: ChipProps): JSX.Element { return ( <div - className={ - "grid place-content-center place-items-center text-xs pb-[0.14rem] px-1.5 border-[1px] rounded-full opacity-70" + - " " + - props.className - } + className={`grid place-content-center place-items-center text-xs pb-[0.14rem] px-1.5 border-[1px] rounded-full opacity-70 ${props.className}`} > {props.children} </div> diff --git a/src/components/InsetBox.tsx b/src/components/InsetBox.tsx new file mode 100644 index 0000000..62b6641 --- /dev/null +++ b/src/components/InsetBox.tsx @@ -0,0 +1,16 @@ +type InsetBoxProps = { + className?: string; + children: React.ReactChild | React.ReactChild[]; + id?: string; +}; + +export default function InsetBox(props: InsetBoxProps): JSX.Element { + return ( + <div + id={props.id} + className={`w-full shadow-inner-sm shadow-dark bg-mid rounded-xl p-8 ${props.className}`} + > + {props.children} + </div> + ); +} diff --git a/src/components/PanelComponents/NavOption.tsx b/src/components/PanelComponents/NavOption.tsx index 898baf5..4189b40 100644 --- a/src/components/PanelComponents/NavOption.tsx +++ b/src/components/PanelComponents/NavOption.tsx @@ -15,7 +15,7 @@ export default function NavOption(props: NavOptionProps): JSX.Element { const divActive = "bg-mid shadow-inner-sm shadow-dark"; const border = "outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent]"; - const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid hover:shadow-inner-sm hover:shadow-dark active:shadow-inner active:shadow-dark transition-all ${ + const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid hover:shadow-inner-sm hover:shadow-dark hover:active:shadow-inner hover:active:shadow-dark transition-all ${ props.border ? border : "" } ${isActive ? divActive : ""}`; diff --git a/src/components/Panels/MainPanel.tsx b/src/components/Panels/MainPanel.tsx index 8685276..94746ec 100644 --- a/src/components/Panels/MainPanel.tsx +++ b/src/components/Panels/MainPanel.tsx @@ -9,6 +9,7 @@ import Markdown from "markdown-to-jsx"; type MainPanelProps = { langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"]; + setLanguagePanelOpen: Function; }; export default function MainPanel(props: MainPanelProps): JSX.Element { @@ -29,7 +30,7 @@ export default function MainPanel(props: MainPanelProps): JSX.Element { /> </div> </Link> - <div className="relative mt-5"> + <div className="relative mt-5" onClick={() => props.setLanguagePanelOpen(true)}> {router.locale ? ( <Button className="absolute right-0 top-[-1.3em] text-xs !py-0.5 !px-2.5"> {router.locale.toUpperCase()} diff --git a/src/graphql/operation.graphql b/src/graphql/operation.graphql index b6c830b..96ef853 100644 --- a/src/graphql/operation.graphql +++ b/src/graphql/operation.graphql @@ -82,6 +82,7 @@ query getEras($language_code: String) { ending_year title(filters: { language: { code: { eq: $language_code } } }) { title + description } } } @@ -124,119 +125,118 @@ query getChronologyItems($language_code: String) { } query getLibraryItemsPreview($language_code: String) { - libraryItems( - filters: { root_item: { eq: true } } - pagination: { limit: -1 } - sort: ["title:asc", "subtitle:asc"] - ) { - data { - id - attributes { - title - subtitle - slug - thumbnail { - data { - attributes { - name - alternativeText - caption - width - height - url - } - } - } - release_date { - year - month - day - } - price { - amount - currency { - data { - attributes { - symbol - code - } - } - } - } - metadata { - __typename - ... on ComponentMetadataBooks { - subtype { - data { - attributes { - slug - titles( - filters: { language: { code: { eq: $language_code } } } - ) { - title - } - } - } - } - } - ... on ComponentMetadataGame { - platform { - data { - attributes { - short - } - } - } - } - ... on ComponentMetadataVideo { - subtype { - data { - attributes { - slug - titles( - filters: { language: { code: { eq: $language_code } } } - ) { - title - } - } - } - } - } - ... on ComponentMetadataAudio { - subtype { - data { - attributes { - slug - titles( - filters: { language: { code: { eq: $language_code } } } - ) { - title - } - } - } - } - } - ... on ComponentMetadataOther { - subtype { - data { - attributes { - slug - titles( - filters: { language: { code: { eq: $language_code } } } - ) { - title - } - } - } - } - } - } - } - } - } + libraryItems( + filters: { root_item: { eq: true } } + pagination: { limit: -1 } + sort: ["title:asc", "subtitle:asc"] + ) { + data { + id + attributes { + title + subtitle + slug + thumbnail { + data { + attributes { + name + alternativeText + caption + width + height + url + } + } + } + release_date { + year + month + day + } + price { + amount + currency { + data { + attributes { + symbol + code + } + } + } + } + metadata { + __typename + ... on ComponentMetadataBooks { + subtype { + data { + attributes { + slug + titles( + filters: { language: { code: { eq: $language_code } } } + ) { + title + } + } + } + } + } + ... on ComponentMetadataGame { + platform { + data { + attributes { + short + } + } + } + } + ... on ComponentMetadataVideo { + subtype { + data { + attributes { + slug + titles( + filters: { language: { code: { eq: $language_code } } } + ) { + title + } + } + } + } + } + ... on ComponentMetadataAudio { + subtype { + data { + attributes { + slug + titles( + filters: { language: { code: { eq: $language_code } } } + ) { + title + } + } + } + } + } + ... on ComponentMetadataOther { + subtype { + data { + attributes { + slug + titles( + filters: { language: { code: { eq: $language_code } } } + ) { + title + } + } + } + } + } + } + } + } + } } - query getLibraryItemsSlugs { libraryItems(pagination: { limit: -1 }) { data { diff --git a/src/graphql/operations-types.ts b/src/graphql/operations-types.ts index 3ab1c72..749d3be 100644 --- a/src/graphql/operations-types.ts +++ b/src/graphql/operations-types.ts @@ -165,6 +165,7 @@ export type GetErasQuery = { title: Array<{ __typename: "ComponentTranslationsChronologyEra"; title: string; + description: string; }>; }; }>; diff --git a/src/pages/library/items/[slug].tsx b/src/pages/library/items/[slug].tsx index dfce15a..18e41ed 100644 --- a/src/pages/library/items/[slug].tsx +++ b/src/pages/library/items/[slug].tsx @@ -32,6 +32,7 @@ import Button from "components/Button"; import HorizontalLine from "components/HorizontalLine"; import AppLayout from "components/AppLayout"; import LibraryItemsPreview from "components/Library/LibraryItemsPreview"; +import InsetBox from "components/InsetBox"; type LibrarySlugProps = { libraryItem: GetLibraryItemQuery; @@ -124,10 +125,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element { )} </div> - <div - id="summary" - className="bg-mid w-full grid place-items-center p-8 rounded-2xl shadow-inner-sm shadow-dark" - > + <InsetBox id="summary" className="grid place-items-center"> <div className="w-[clamp(0px,100%,42rem)] grid place-items-center gap-8"> {item.subitem_of.data.length > 0 ? ( <div className="grid place-items-center"> @@ -159,7 +157,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element { "" )} </div> - </div> + </InsetBox> {item.gallery.data.length > 0 ? ( <div id="gallery" className="grid place-items-center gap-8 w-full"> @@ -188,9 +186,9 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element { "" )} - <div + <InsetBox id="details" - className="bg-mid w-full grid place-items-center p-8 rounded-2xl text-left shadow-inner-sm shadow-dark" + className="grid place-items-center" > <div className="w-[clamp(0px,100%,42rem)] grid place-items gap-8"> <h2 className="text-2xl text-center"> @@ -345,7 +343,7 @@ export default function LibrarySlug(props: LibrarySlugProps): JSX.Element { "" )} </div> - </div> + </InsetBox> {item.subitems.data.length > 0 ? ( item.metadata.length > 0 && diff --git a/src/pages/wiki/chronology.tsx b/src/pages/wiki/chronology.tsx index cd5203f..78f5969 100644 --- a/src/pages/wiki/chronology.tsx +++ b/src/pages/wiki/chronology.tsx @@ -16,6 +16,8 @@ import NavOption from "components/PanelComponents/NavOption"; import ReturnButton from "components/PanelComponents/ReturnButton"; import HorizontalLine from "components/HorizontalLine"; import AppLayout from "components/AppLayout"; +import { prettySlug } from "queries/helpers"; +import InsetBox from "components/InsetBox"; interface DataChronologyProps { chronologyItems: GetChronologyItemsQuery; @@ -27,20 +29,39 @@ export default function DataChronology( props: DataChronologyProps ): JSX.Element { const langui = props.langui.websiteInterfaces.data[0].attributes; + const chronologyItems = props.chronologyItems.chronologyItems; + const chronologyEras = props.chronologyEras.chronologyEras; // Group by year the Chronology items - let chronologyItemYearGroups: GetChronologyItemsQuery["chronologyItems"]["data"][number][][] = + let chronologyItemYearGroups: GetChronologyItemsQuery["chronologyItems"]["data"][number][][][] = []; - if (props.chronologyItems.chronologyItems) { - props.chronologyItems.chronologyItems.data.map((item) => { - if (!chronologyItemYearGroups.hasOwnProperty(item.attributes.year)) { - chronologyItemYearGroups[item.attributes.year] = [item]; - } else { - chronologyItemYearGroups[item.attributes.year].push(item); - } - }); - } + chronologyEras.data.map((era) => { + chronologyItemYearGroups.push([]); + }); + + let currentChronologyEraIndex = 0; + chronologyItems.data.map((item) => { + if ( + item.attributes.year > + chronologyEras.data[currentChronologyEraIndex].attributes.ending_year + ) { + currentChronologyEraIndex++; + } + if ( + !chronologyItemYearGroups[currentChronologyEraIndex].hasOwnProperty( + item.attributes.year + ) + ) { + chronologyItemYearGroups[currentChronologyEraIndex][ + item.attributes.year + ] = [item]; + } else { + chronologyItemYearGroups[currentChronologyEraIndex][ + item.attributes.year + ].push(item); + } + }); const subPanel = ( <SubPanel> @@ -51,7 +72,7 @@ export default function DataChronology( <NavOption key={era.id} url={"#" + era.attributes.slug} - title={era.attributes.title[0] ? era.attributes.title[0].title : ""} + title={era.attributes.title.length > 0 ? era.attributes.title[0].title : prettySlug(era.attributes.slug)} subtitle={ era.attributes.starting_year + " → " + era.attributes.ending_year } @@ -63,17 +84,32 @@ export default function DataChronology( const contentPanel = ( <ContentPanel> - {chronologyItemYearGroups.map((items, index: number) => { - if (items && items[0].attributes.year) { - return ( + {chronologyItemYearGroups.map((era, eraIndex) => ( + <> + <InsetBox + id={chronologyEras.data[eraIndex].attributes.slug} + className="grid text-center my-8 gap-4" + > + <h2 className="text-2xl"> + {chronologyEras.data[eraIndex].attributes.title.length > 0 + ? chronologyEras.data[eraIndex].attributes.title[0].title + : prettySlug(chronologyEras.data[eraIndex].attributes.slug)} + </h2> + <p className="whitespace-pre-line "> + {chronologyEras.data[eraIndex].attributes.title.length > 0 + ? chronologyEras.data[eraIndex].attributes.title[0].description + : ""} + </p> + </InsetBox> + {era.map((items, index) => ( <ChronologyYearComponent - key={index} + key={`${eraIndex}-${index}`} year={items[0].attributes.year} items={items} /> - ); - } - })} + ))} + </> + ))} </ContentPanel> ); diff --git a/src/queries/helpers.ts b/src/queries/helpers.ts index 4dc1a66..7bbfe01 100644 --- a/src/queries/helpers.ts +++ b/src/queries/helpers.ts @@ -51,7 +51,7 @@ export function prettyinlineTitle( export function prettyItemType( metadata: { - __typename: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number]["__typename"] + __typename: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number]["__typename"]; }, langui: GetWebsiteInterfaceQuery["websiteInterfaces"]["data"][number]["attributes"] ): string { @@ -73,7 +73,7 @@ export function prettyItemType( } export function prettyItemSubType( - metadata: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number], + metadata: GetLibraryItemsPreviewQuery["libraryItems"]["data"][number]["attributes"]["metadata"][number] ): string { switch (metadata.__typename) { case "ComponentMetadataAudio": @@ -92,6 +92,25 @@ export function prettyItemSubType( } } +export function prettyLanguage(code: string): string { + switch (code) { + case "en": + return "English"; + case "es": + return "Español"; + case "fr": + return "Français"; + case "ja": + return "日本語"; + case "en": + return "English"; + case "xx": + return "██"; + default: + return code; + } +} + export function capitalizeString(string: string): string { function capitalizeWord(word: string): string { return word.charAt(0).toUpperCase() + word.substring(1); diff --git a/src/tailwind.css b/src/tailwind.css index 40f1863..f8d407c 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -9,7 +9,7 @@ } * { - @apply box-border font-body font-medium scroll-smooth; + @apply box-border font-body font-medium scroll-smooth scroll-m-8; } h1, @@ -25,10 +25,6 @@ @apply transition-colors underline-offset-2 decoration-dotted underline decoration-dark hover:text-dark; } - *:target { - @apply scroll-mt-4; - } - *::selection { @apply bg-dark text-light; } diff --git a/tailwind.config.js b/tailwind.config.js index 692398b..ea65354 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -79,7 +79,7 @@ module.exports = { ".drop-shadow-dark-2xl": { filter: "drop-shadow(0 25px 25px rgb(156 102 68 / 0.8))", }, - + ".drop-shadow-black-lg": { filter: "drop-shadow(0 10px 8px rgb(27 24 17 / 0.2)) drop-shadow(0 4px 3px rgb(27 24 17 / 0.4))",