From add0522095c1ff37e5fc8c557842aca51b6e7455 Mon Sep 17 00:00:00 2001
From: DrMint <thomas@barillot.net>
Date: Sat, 23 Apr 2022 20:16:23 +0200
Subject: [PATCH] Fused all the preview cards to one all-encompassing component

---
 .../{ContentTOCLine.tsx => ContentLine.tsx}   |   2 +-
 .../Library/LibraryContentPreview.tsx         |  61 ------
 .../Library/LibraryItemsPreview.tsx           |  98 ---------
 src/components/News/PostsPreview.tsx          |  62 ------
 .../{Content => }/ThumbnailHeader.tsx         |   0
 src/components/ThumbnailPreview.tsx           | 193 ++++++++++++++++++
 src/components/Videos/VideoPreview.tsx        |  80 --------
 .../Chronology/ChronologyItemComponent.tsx    |   0
 .../Chronology/ChronologyYearComponent.tsx    |   2 +-
 9 files changed, 195 insertions(+), 303 deletions(-)
 rename src/components/Library/{ContentTOCLine.tsx => ContentLine.tsx} (95%)
 delete mode 100644 src/components/Library/LibraryContentPreview.tsx
 delete mode 100644 src/components/Library/LibraryItemsPreview.tsx
 delete mode 100644 src/components/News/PostsPreview.tsx
 rename src/components/{Content => }/ThumbnailHeader.tsx (100%)
 create mode 100644 src/components/ThumbnailPreview.tsx
 delete mode 100644 src/components/Videos/VideoPreview.tsx
 rename src/components/{ => Wiki}/Chronology/ChronologyItemComponent.tsx (100%)
 rename src/components/{ => Wiki}/Chronology/ChronologyYearComponent.tsx (86%)

diff --git a/src/components/Library/ContentTOCLine.tsx b/src/components/Library/ContentLine.tsx
similarity index 95%
rename from src/components/Library/ContentTOCLine.tsx
rename to src/components/Library/ContentLine.tsx
index 782c047..0419d53 100644
--- a/src/components/Library/ContentTOCLine.tsx
+++ b/src/components/Library/ContentLine.tsx
@@ -20,7 +20,7 @@ interface Props {
   langui: AppStaticProps["langui"];
 }
 
-export default function ContentTOCLine(props: Props): JSX.Element {
+export default function ContentLine(props: Props): JSX.Element {
   const { content, langui, parentSlug } = props;
 
   const [opened, setOpened] = useState(false);
diff --git a/src/components/Library/LibraryContentPreview.tsx b/src/components/Library/LibraryContentPreview.tsx
deleted file mode 100644
index 0f44a4f..0000000
--- a/src/components/Library/LibraryContentPreview.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import Chip from "components/Chip";
-import Img, { ImageQuality } from "components/Img";
-import { GetContentsQuery } from "graphql/generated";
-import Link from "next/link";
-import { prettySlug } from "queries/helpers";
-
-interface Props {
-  item: Exclude<
-    GetContentsQuery["contents"],
-    null | undefined
-  >["data"][number]["attributes"];
-}
-
-export default function LibraryContentPreview(props: Props): JSX.Element {
-  const { item } = props;
-
-  return (
-    <Link href={`/contents/${item?.slug}`} passHref>
-      <div className="drop-shadow-shade-xl cursor-pointer grid items-end fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform">
-        {item?.thumbnail?.data?.attributes ? (
-          <Img
-            className="rounded-md coarse:rounded-b-none"
-            image={item.thumbnail.data.attributes}
-            quality={ImageQuality.Medium}
-          />
-        ) : (
-          <div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
-        )}
-        <div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
-          <div className="grid grid-flow-col gap-1 overflow-hidden place-content-start">
-            {item?.type?.data?.attributes && (
-              <Chip>
-                {item.type.data.attributes.titles?.[0]
-                  ? item.type.data.attributes.titles[0]?.title
-                  : prettySlug(item.type.data.attributes.slug)}
-              </Chip>
-            )}
-          </div>
-          <div>
-            {item?.titles?.[0] ? (
-              <>
-                <p>{item.titles[0].pre_title}</p>
-                <h1 className="text-lg">{item.titles[0].title}</h1>
-                <h2>{item.titles[0].subtitle}</h2>
-              </>
-            ) : (
-              <h1 className="text-lg">{prettySlug(item?.slug)}</h1>
-            )}
-          </div>
-          <div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
-            {item?.categories?.data.map((category) => (
-              <Chip key={category.id} className="text-sm">
-                {category.attributes?.short}
-              </Chip>
-            ))}
-          </div>
-        </div>
-      </div>
-    </Link>
-  );
-}
diff --git a/src/components/Library/LibraryItemsPreview.tsx b/src/components/Library/LibraryItemsPreview.tsx
deleted file mode 100644
index 4f06ece..0000000
--- a/src/components/Library/LibraryItemsPreview.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import Chip from "components/Chip";
-import Img, { ImageQuality } from "components/Img";
-import { useAppLayout } from "contexts/AppLayoutContext";
-import {
-  GetLibraryItemQuery,
-  GetLibraryItemsPreviewQuery,
-} from "graphql/generated";
-import Link from "next/link";
-import { AppStaticProps } from "queries/getAppStaticProps";
-import { prettyDate, prettyItemSubType, prettyPrice } from "queries/helpers";
-
-interface Props {
-  className?: string;
-  item:
-    | Exclude<
-        Exclude<
-          Exclude<
-            GetLibraryItemQuery["libraryItems"],
-            null | undefined
-          >["data"][number]["attributes"],
-          null | undefined
-        >["subitems"],
-        null | undefined
-      >["data"][number]["attributes"]
-    | Exclude<
-        GetLibraryItemsPreviewQuery["libraryItems"],
-        null | undefined
-      >["data"][number]["attributes"];
-  currencies: AppStaticProps["currencies"];
-}
-
-export default function LibraryItemsPreview(props: Props): JSX.Element {
-  const { item } = props;
-  const appLayout = useAppLayout();
-
-  return (
-    <Link href={`/library/${item?.slug}`} passHref>
-      <div
-        className={`drop-shadow-shade-xl cursor-pointer grid items-end hover:rounded-3xl fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02] transition-transform ${props.className}`}
-      >
-        {item?.thumbnail?.data?.attributes ? (
-          <Img
-            image={item.thumbnail.data.attributes}
-            quality={ImageQuality.Small}
-          />
-        ) : (
-          <div className="w-full aspect-[21/29.7] bg-light rounded-lg"></div>
-        )}
-
-        <div className="linearbg-obi fine:drop-shadow-shade-lg fine:absolute place-items-start bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)] transition-opacity z-20 grid p-4 gap-2">
-          {item?.metadata && item.metadata.length > 0 && item.metadata[0] && (
-            <div className="flex flex-row gap-1">
-              <Chip>{prettyItemSubType(item.metadata[0])}</Chip>
-            </div>
-          )}
-
-          <div>
-            <h2 className="mobile:text-sm text-lg leading-5">{item?.title}</h2>
-            <h3 className="mobile:text-xs leading-3">{item?.subtitle}</h3>
-          </div>
-
-          <div className="w-full grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:h-0 [scrollbar-width:none] place-content-start">
-            {item?.categories?.data.map((category) => (
-              <Chip key={category.id} className="text-sm">
-                {category.attributes?.short}
-              </Chip>
-            ))}
-          </div>
-
-          {(item?.release_date || item?.price) && (
-            <div className="grid grid-flow-col w-full">
-              {item.release_date && (
-                <p className="mobile:text-xs text-sm">
-                  <span className="material-icons !text-base translate-y-[.15em] mr-1">
-                    event
-                  </span>
-                  {prettyDate(item.release_date)}
-                </p>
-              )}
-              {item.price && (
-                <p className="mobile:text-xs text-sm justify-self-end">
-                  <span className="material-icons !text-base translate-y-[.15em] mr-1">
-                    shopping_cart
-                  </span>
-                  {prettyPrice(
-                    item.price,
-                    props.currencies,
-                    appLayout.currency
-                  )}
-                </p>
-              )}
-            </div>
-          )}
-        </div>
-      </div>
-    </Link>
-  );
-}
diff --git a/src/components/News/PostsPreview.tsx b/src/components/News/PostsPreview.tsx
deleted file mode 100644
index b1cd585..0000000
--- a/src/components/News/PostsPreview.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import Chip from "components/Chip";
-import Img, { ImageQuality } from "components/Img";
-import { GetPostsPreviewQuery } from "graphql/generated";
-import Link from "next/link";
-import { prettyDate, prettySlug } from "queries/helpers";
-
-interface Props {
-  post: Exclude<
-    GetPostsPreviewQuery["posts"],
-    null | undefined
-  >["data"][number]["attributes"];
-}
-
-export default function PostPreview(props: Props): JSX.Element {
-  const { post } = props;
-
-  return (
-    <Link href={`/news/${post?.slug}`} passHref>
-      <div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] transition-transform">
-        {post?.thumbnail?.data?.attributes ? (
-          <Img
-            className="rounded-md rounded-b-none"
-            image={post.thumbnail.data.attributes}
-            quality={ImageQuality.Medium}
-          />
-        ) : (
-          <div className="w-full aspect-[3/2] bg-light rounded-lg"></div>
-        )}
-        <div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
-          {post?.date && (
-            <div className="grid grid-flow-col w-full">
-              <p className="mobile:text-xs text-sm">
-                <span className="material-icons !text-base translate-y-[.15em] mr-1">
-                  event
-                </span>
-                {prettyDate(post.date)}
-              </p>
-            </div>
-          )}
-
-          <div>
-            {post?.translations?.[0] ? (
-              <>
-                <h1 className="text-xl">{post.translations[0].title}</h1>
-                <p>{post.translations[0].excerpt}</p>
-              </>
-            ) : (
-              <h1 className="text-lg">{prettySlug(post?.slug)}</h1>
-            )}
-          </div>
-          <div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
-            {post?.categories?.data.map((category) => (
-              <Chip key={category.id} className="text-sm">
-                {category.attributes?.short}
-              </Chip>
-            ))}
-          </div>
-        </div>
-      </div>
-    </Link>
-  );
-}
diff --git a/src/components/Content/ThumbnailHeader.tsx b/src/components/ThumbnailHeader.tsx
similarity index 100%
rename from src/components/Content/ThumbnailHeader.tsx
rename to src/components/ThumbnailHeader.tsx
diff --git a/src/components/ThumbnailPreview.tsx b/src/components/ThumbnailPreview.tsx
new file mode 100644
index 0000000..94e8c2e
--- /dev/null
+++ b/src/components/ThumbnailPreview.tsx
@@ -0,0 +1,193 @@
+import { useAppLayout } from "contexts/AppLayoutContext";
+import {
+  DatePickerFragment,
+  PricePickerFragment,
+  UploadImageFragment,
+} from "graphql/generated";
+import Link from "next/link";
+import { AppStaticProps } from "queries/getAppStaticProps";
+import {
+  prettyDate,
+  prettyDuration,
+  prettyPrice,
+  prettyShortenNumber,
+} from "queries/helpers";
+import Chip from "./Chip";
+import Img, { ImageQuality } from "./Img";
+
+interface Props {
+  thumbnail?: UploadImageFragment | string | null | undefined;
+  thumbnailAspectRatio?: string;
+  href: string;
+  pre_title?: string | null | undefined;
+  title: string | null | undefined;
+  subtitle?: string | null | undefined;
+  description?: string | null | undefined;
+  topChips?: string[];
+  bottomChips?: string[];
+  keepInfoVisible?: boolean;
+  metadata?: {
+    currencies?: AppStaticProps["currencies"];
+    release_date?: DatePickerFragment | null;
+    price?: PricePickerFragment | null;
+    views?: number;
+    author?: string;
+    position: "Bottom" | "Top";
+  };
+  hoverlay?: {
+    __typename: "Video";
+    duration: number;
+  };
+}
+
+export default function ThumbnailPreview(props: Props): JSX.Element {
+  const {
+    href,
+    thumbnail,
+    pre_title,
+    title,
+    subtitle,
+    description,
+    topChips,
+    bottomChips,
+    keepInfoVisible,
+    thumbnailAspectRatio,
+    metadata,
+    hoverlay,
+  } = props;
+
+  const appLayout = useAppLayout();
+
+  const metadataJSX =
+    metadata && (metadata.release_date || metadata.price) ? (
+      <div className="flex flex-row flex-wrap gap-x-3 w-full">
+        {metadata.release_date && (
+          <p className="mobile:text-xs text-sm">
+            <span className="material-icons !text-base translate-y-[.15em] mr-1">
+              event
+            </span>
+            {prettyDate(metadata.release_date)}
+          </p>
+        )}
+        {metadata.price && metadata.currencies && (
+          <p className="mobile:text-xs text-sm justify-self-end">
+            <span className="material-icons !text-base translate-y-[.15em] mr-1">
+              shopping_cart
+            </span>
+            {prettyPrice(
+              metadata.price,
+              metadata.currencies,
+              appLayout.currency
+            )}
+          </p>
+        )}
+        {metadata.views && (
+          <p className="mobile:text-xs text-sm">
+            <span className="material-icons !text-base translate-y-[.15em] mr-1">
+              visibility
+            </span>
+            {prettyShortenNumber(metadata.views)}
+          </p>
+        )}
+        {metadata.author && (
+          <p className="mobile:text-xs text-sm">
+            <span className="material-icons !text-base translate-y-[.15em] mr-1">
+              person
+            </span>
+            {metadata.author}
+          </p>
+        )}
+      </div>
+    ) : (
+      <></>
+    );
+
+  return (
+    <Link href={href} passHref>
+      <div
+        className="drop-shadow-shade-xl cursor-pointer grid items-end
+        fine:[--cover-opacity:0] hover:[--cover-opacity:1] hover:scale-[1.02]
+        [--bg-opacity:0] hover:[--bg-opacity:0.5] [--play-opacity:0]
+        hover:[--play-opacity:100] transition-transform"
+      >
+        {thumbnail ? (
+          <div className="relative">
+            <Img
+              className={
+                keepInfoVisible
+                  ? "rounded-t-md"
+                  : "rounded-md coarse:rounded-b-none"
+              }
+              image={thumbnail}
+              quality={ImageQuality.Medium}
+            />
+            {hoverlay && hoverlay.__typename === "Video" && (
+              <>
+                <div
+                  className="absolute inset-0 text-light grid
+                  place-content-center drop-shadow-shade-lg bg-shade
+                  bg-opacity-[var(--bg-opacity)] transition-colors"
+                >
+                  <span
+                    className="material-icons text-6xl
+                    opacity-[var(--play-opacity)] transition-opacity"
+                  >
+                    play_circle_outline
+                  </span>
+                </div>
+                <div
+                  className="absolute right-2 bottom-2 text-light bg-black
+                  bg-opacity-60 px-2 rounded-full"
+                >
+                  {prettyDuration(hoverlay.duration)}
+                </div>
+              </>
+            )}
+          </div>
+        ) : (
+          <div
+            className={`w-full aspect-[${thumbnailAspectRatio}] bg-light ${
+              keepInfoVisible
+                ? "rounded-t-md"
+                : "rounded-md coarse:rounded-b-none"
+            }`}
+          ></div>
+        )}
+        <div
+          className={`linearbg-obi ${
+            keepInfoVisible
+              ? "-mt-[0.3333em]"
+              : `fine:drop-shadow-shade-lg fine:absolute coarse:rounded-b-md
+              bottom-2 -inset-x-0.5 opacity-[var(--cover-opacity)]`
+          } transition-opacity z-20 grid p-4 gap-2`}
+        >
+          {metadata?.position === "Top" && metadataJSX}
+          {topChips && topChips.length > 0 && (
+            <div className="grid grid-flow-col gap-1 overflow-hidden place-content-start">
+              {topChips.map((text, index) => (
+                <Chip key={index}>{text}</Chip>
+              ))}
+            </div>
+          )}
+          <div>
+            {pre_title && <p>{pre_title}</p>}
+            {title && <h1 className="text-lg">{title}</h1>}
+            {subtitle && <h2>{subtitle}</h2>}
+          </div>
+          {description && <p>{description}</p>}
+          {bottomChips && bottomChips.length > 0 && (
+            <div className="grid grid-flow-col gap-1 overflow-hidden place-content-start">
+              {bottomChips.map((text, index) => (
+                <Chip key={index} className="text-sm">
+                  {text}
+                </Chip>
+              ))}
+            </div>
+          )}
+
+          {metadata?.position === "Bottom" && metadataJSX}
+        </div>
+      </div>
+    </Link>
+  );
+}
diff --git a/src/components/Videos/VideoPreview.tsx b/src/components/Videos/VideoPreview.tsx
deleted file mode 100644
index 7fec600..0000000
--- a/src/components/Videos/VideoPreview.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import Chip from "components/Chip";
-import { GetVideosPreviewQuery } from "graphql/generated";
-import Link from "next/link";
-import {
-  getVideoThumbnailURL,
-  prettyDate,
-  prettyDuration,
-  prettyShortenNumber,
-} from "queries/helpers";
-
-interface Props {
-  video: Exclude<
-    Exclude<
-      GetVideosPreviewQuery["videos"],
-      null | undefined
-    >["data"][number]["attributes"],
-    null | undefined
-  >;
-}
-
-export default function PostPreview(props: Props): JSX.Element {
-  const { video } = props;
-
-  return (
-    <Link href={`/archives/videos/v/${video.uid}`} passHref>
-      <div className="drop-shadow-shade-xl cursor-pointer grid items-end hover:scale-[1.02] [--bg-opacity:0] hover:[--bg-opacity:0.5] [--play-opacity:0] hover:[--play-opacity:100] transition-transform">
-        <div className="relative">
-          <img
-            className="aspect-video rounded-t-lg"
-            src={getVideoThumbnailURL(video.uid)}
-            alt={video.title}
-          />
-          <div className="absolute inset-0 text-light grid place-content-center drop-shadow-shade-lg bg-shade bg-opacity-[var(--bg-opacity)] transition-colors">
-            <span className="material-icons text-6xl opacity-[var(--play-opacity)] transition-opacity">
-              play_circle_outline
-            </span>
-          </div>
-          <div className="absolute right-2 bottom-2 text-light bg-black bg-opacity-60 px-2 rounded-full">
-            {prettyDuration(video.duration)}
-          </div>
-        </div>
-        <div className="linearbg-obi fine:drop-shadow-shade-lg rounded-b-md top-full transition-opacity z-20 grid p-4 gap-2">
-          <div className="flex flex-row flex-wrap gap-x-3 w-full">
-            <p className="mobile:text-xs text-sm">
-              <span className="material-icons !text-base translate-y-[.15em] mr-1">
-                event
-              </span>
-              {prettyDate(video.published_date)}
-            </p>
-            <p className="mobile:text-xs text-sm">
-              <span className="material-icons !text-base translate-y-[.15em] mr-1">
-                visibility
-              </span>
-              {prettyShortenNumber(video.views)}
-            </p>
-            {video.channel?.data?.attributes && (
-              <p className="mobile:text-xs text-sm">
-                <span className="material-icons !text-base translate-y-[.15em] mr-1">
-                  person
-                </span>
-                {video.channel.data.attributes.title}
-              </p>
-            )}
-          </div>
-
-          <div>
-            <h1 className="text-xl">{video.title}</h1>
-          </div>
-          <div className="grid grid-flow-col gap-1 overflow-x-scroll webkit-scrollbar:w-0 [scrollbar-width:none] place-content-start">
-            {video.categories?.data.map((category) => (
-              <Chip key={category.id} className="text-sm">
-                {category.attributes?.short}
-              </Chip>
-            ))}
-          </div>
-        </div>
-      </div>
-    </Link>
-  );
-}
diff --git a/src/components/Chronology/ChronologyItemComponent.tsx b/src/components/Wiki/Chronology/ChronologyItemComponent.tsx
similarity index 100%
rename from src/components/Chronology/ChronologyItemComponent.tsx
rename to src/components/Wiki/Chronology/ChronologyItemComponent.tsx
diff --git a/src/components/Chronology/ChronologyYearComponent.tsx b/src/components/Wiki/Chronology/ChronologyYearComponent.tsx
similarity index 86%
rename from src/components/Chronology/ChronologyYearComponent.tsx
rename to src/components/Wiki/Chronology/ChronologyYearComponent.tsx
index 53c6c54..69ba5b6 100644
--- a/src/components/Chronology/ChronologyYearComponent.tsx
+++ b/src/components/Wiki/Chronology/ChronologyYearComponent.tsx
@@ -1,4 +1,4 @@
-import ChronologyItemComponent from "components/Chronology/ChronologyItemComponent";
+import ChronologyItemComponent from "components/Wiki/Chronology/ChronologyItemComponent";
 import { GetChronologyItemsQuery } from "graphql/generated";
 import { AppStaticProps } from "queries/getAppStaticProps";