From 8071078a8cb652a93cdf48366c583f5c1a798e70 Mon Sep 17 00:00:00 2001 From: Riley Abrahamson <32375220+RileyAbr@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:34:06 -0800 Subject: [PATCH] Revert "Convert Package page use a client component" This reverts commit 91d2cdc2c3cfb7dd2aeb5afcdac647e2ee5f6871. --- .../[packageScope]/[packageName]/page.tsx | 493 ++++++++++++++++- .../src/components/PackageView.tsx | 495 ------------------ 2 files changed, 488 insertions(+), 500 deletions(-) delete mode 100644 wally-registry-frontend/src/components/PackageView.tsx diff --git a/wally-registry-frontend/src/app/package/[packageScope]/[packageName]/page.tsx b/wally-registry-frontend/src/app/package/[packageScope]/[packageName]/page.tsx index 4067bed..666d895 100644 --- a/wally-registry-frontend/src/app/package/[packageScope]/[packageName]/page.tsx +++ b/wally-registry-frontend/src/app/package/[packageScope]/[packageName]/page.tsx @@ -1,12 +1,495 @@ -import PackageView from "@/components/PackageView" -import { Suspense } from "react" +"use client" + +import { isMobile, notMobile } from "@/breakpoints" +import { Button } from "@/components/Button" +import ContentSection from "@/components/ContentSection" +import CopyCode from "@/components/CopyCode" +import NotFoundMessage from "@/components/NotFoundMessage" +import { Heading, Paragraph } from "@/components/Typography" +import { + buildWallyPackageDownloadLink, + getWallyPackageMetadata, +} from "@/services/wally.api" +import { WallyPackageMetadata } from "@/types/wally" +import capitalize from "@/utils/capitalize" +import { useParams, useRouter, useSearchParams } from "next/navigation" +import React, { useEffect, useState } from "react" +import styled from "styled-components" + +type WidthVariation = "full" | "half" + +interface StyledMetaItemProps { + width: WidthVariation +} + +const FlexColumns = styled.article` + display: flex; + flex-flow: row nowrap; + width: 100%; + min-height: 65vh; + + @media screen and (${isMobile}) { + flex-flow: row wrap; + } +` + +const WideColumn = styled.section` + width: 65%; + + @media screen and (${notMobile}) { + border-right: solid 2px rgba(0, 0, 0, 0.1); + } + + @media screen and (${isMobile}) { + width: 100%; + border-bottom: solid 2px rgba(0, 0, 0, 0.1); + } +` + +const NarrowColumn = styled.aside` + width: 35%; + + @media screen and (${notMobile}) { + padding-left: 1rem; + } + + @media screen and (${isMobile}) { + padding-top: 0.5rem; + width: 100%; + } +` + +const MetaHeader = styled.h2` + width: 100%; + font-size: 2rem; +` + +const MetaSubheader = styled.b` + font-weight: bold; + display: block; + font-size: 1.1rem; +` + +const MetaItemWrapper = styled.div` + width: ${(props) => (props.width === "full" ? "100%" : "50%")}; + display: inline-block; + margin: 0.5rem 0; + white-space: nowrap; + text-overflow: ellipsis; + + a:hover, + a:focus { + text-decoration: underline; + color: var(--wally-red); + } +` + +const VersionSelect = styled.select` + &:hover { + cursor: pointer; + } + + &:hover, + &:focus { + text-decoration: underline; + color: var(--wally-red); + } + + > option { + color: var(--wally-mauve); + } +` + +const AuthorItem = styled.p` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const DependencyLinkWrapper = styled.div` + display: block; + position: relative; + width: 100%; + + &:hover { + > span { + visibility: visible; + } + } +` + +const DependencyLinkItem = styled.a` + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const DependencyLinkTooltip = styled.span` + visibility: hidden; + position: absolute; + z-index: 2; + color: white; + font-size: 0.8rem; + background-color: var(--wally-brown); + border-radius: 5px; + padding: 10px; + top: -45px; + left: 50%; + transform: translateX(-50%); + + &::before { + content: ""; + position: absolute; + transform: rotate(45deg); + background-color: var(--wally-brown); + padding: 6px; + z-index: 1; + top: 77%; + left: 45%; + } +` + +const StyledDownloadLink = styled.a` + display: inline-block; + height: 1rem; + width: auto; + padding-right: 0.3rem; + cursor: pointer; +` + +const MetaItem = ({ + title, + width, + children, +}: { + title: string + width?: WidthVariation + children: React.ReactNode +}) => { + return ( + + {title} + {children} + + ) +} + +const DependencyLink = ({ packageInfo }: { packageInfo: string }) => { + const packageMatch = packageInfo.match(/(.+\/.+)@[^\d]+([\d.]+)/) + if (packageMatch != null) { + const name = packageMatch[1] + const version = packageMatch[2] + return ( + + + {name + "@" + version} + + {name + "@" + version} + + ) + } + return {packageInfo} +} + +const DownloadLink = ({ + url, + filename, + children, +}: { + url: string + filename: string + children: React.ReactNode +}) => { + const handleAction = async () => { + // Using bare JavaScript mutations over a React ref keeps this link tag keyboard accessible, because you can't include an href on the base anchor tag and overwrite it with a ref, and we need an href to ensure full keyboard compatibility + const link = document.createElement("a") + + const result = await fetch(url, { + headers: { + "wally-version": "0.3.2", + }, + }) + + const blob = await result.blob() + const href = window.URL.createObjectURL(blob) + + link.href = href + link.download = filename + + document.body.appendChild(link) + + link.click() + + link.parentNode?.removeChild(link) + } + + return ( + <> + + {children} + + + ) +} + +const DownloadIcon = ({ packageName }: { packageName: string }) => ( + + Download a Wally Package + Downloads {packageName} + + + + + +) + +type PackageParams = { + packageScope: string + packageName: string +} export default function Package() { + const searchParams = useSearchParams() + const router = useRouter() + + const { packageScope, packageName } = useParams() + const [packageHistory, setPackageHistory] = useState<[WallyPackageMetadata]>() + const [packageVersion, setPackageVersion] = useState() + const [isLoaded, setIsLoaded] = useState(false) + const [isError, setIsError] = useState(false) + + const urlPackageVersion = searchParams.get("version") + if (urlPackageVersion != null && urlPackageVersion !== packageVersion) { + setPackageVersion(urlPackageVersion) + } + + const loadPackageData = async (packageScope: string, packageName: string) => { + const packageData = await getWallyPackageMetadata(packageScope, packageName) + + if (packageData == undefined) { + setIsError(true) + setIsLoaded(true) + return + } + + const filteredPackageData = packageData.versions.some( + (pack: WallyPackageMetadata) => !pack.package.version.includes("-") + ) + ? packageData.versions.filter( + (pack: WallyPackageMetadata) => !pack.package.version.includes("-") + ) + : packageData.versions + + setPackageHistory(filteredPackageData) + + if (urlPackageVersion == null) { + const latestVersion = filteredPackageData[0].package.version + setPackageVersion(latestVersion) + router.replace( + `/package/${packageScope}/${packageName}?version=${latestVersion}` + ) + } + + setIsLoaded(true) + } + + useEffect(() => { + loadPackageData(packageScope, packageName) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [packageScope, packageName]) + + if (!isLoaded) { + return ( + <> + + Loading... + + + ) + } + + if (isError) { + return ( + <> + + + + + ) + } + + const packageMetadata = packageHistory?.find( + (item: WallyPackageMetadata) => item.package.version === packageVersion + ) + + if (packageMetadata == undefined) { + return ( + <> + + {packageName} + + + Couldn't find {capitalize(packageName)} version{" "} + {packageVersion}. Are you sure that's a valid version? + + + + + + ) + } + return ( <> - - - + + + + {packageName} + + + {packageMetadata.package.description ?? + `${capitalize(packageName)} has no provided description.`} + + + + + Metadata + + + + + + + { + router.push( + `/package/${packageScope}/${packageName}?version=${a.target.value}` + ) + }} + > + {packageHistory?.map((item: WallyPackageMetadata) => { + return ( + + ) + })} + + + + {packageMetadata.package.license && ( + + + {packageMetadata?.package.license} + + + )} + + + + + + + + + {capitalize(packageMetadata.package.realm)} + + + {/* TODO: Re-implement when Wally API supports custom source repos */} + {/* {packageMetadata?.package.registry && ( + + + {packageMetadata?.package.registry.replace("https://", "")} + + + )} */} + + {packageMetadata.package.authors.length > 0 && ( + + {packageMetadata.package.authors.map((author) => ( + {author} + ))} + + )} + + {Object.keys(packageMetadata.dependencies).length > 0 && ( + + {Object.values(packageMetadata.dependencies).map( + (dependency) => ( + + ) + )} + + )} + + {Object.keys(packageMetadata["server-dependencies"]).length > 0 && ( + + {Object.values(packageMetadata["server-dependencies"]).map( + (dependency) => ( + + ) + )} + + )} + + {Object.keys(packageMetadata["dev-dependencies"]).length > 0 && ( + + {Object.values(packageMetadata["dev-dependencies"]).map( + (dependency) => ( + + ) + )} + + )} + + + ) } diff --git a/wally-registry-frontend/src/components/PackageView.tsx b/wally-registry-frontend/src/components/PackageView.tsx deleted file mode 100644 index bd5f98c..0000000 --- a/wally-registry-frontend/src/components/PackageView.tsx +++ /dev/null @@ -1,495 +0,0 @@ -"use client" - -import { isMobile, notMobile } from "@/breakpoints" -import { Button } from "@/components/Button" -import ContentSection from "@/components/ContentSection" -import CopyCode from "@/components/CopyCode" -import NotFoundMessage from "@/components/NotFoundMessage" -import { Heading, Paragraph } from "@/components/Typography" -import { - buildWallyPackageDownloadLink, - getWallyPackageMetadata, -} from "@/services/wally.api" -import { WallyPackageMetadata } from "@/types/wally" -import capitalize from "@/utils/capitalize" -import { useParams, useRouter, useSearchParams } from "next/navigation" -import React, { useEffect, useState } from "react" -import styled from "styled-components" - -type WidthVariation = "full" | "half" - -interface StyledMetaItemProps { - width: WidthVariation -} - -const FlexColumns = styled.article` - display: flex; - flex-flow: row nowrap; - width: 100%; - min-height: 65vh; - - @media screen and (${isMobile}) { - flex-flow: row wrap; - } -` - -const WideColumn = styled.section` - width: 65%; - - @media screen and (${notMobile}) { - border-right: solid 2px rgba(0, 0, 0, 0.1); - } - - @media screen and (${isMobile}) { - width: 100%; - border-bottom: solid 2px rgba(0, 0, 0, 0.1); - } -` - -const NarrowColumn = styled.aside` - width: 35%; - - @media screen and (${notMobile}) { - padding-left: 1rem; - } - - @media screen and (${isMobile}) { - padding-top: 0.5rem; - width: 100%; - } -` - -const MetaHeader = styled.h2` - width: 100%; - font-size: 2rem; -` - -const MetaSubheader = styled.b` - font-weight: bold; - display: block; - font-size: 1.1rem; -` - -const MetaItemWrapper = styled.div` - width: ${(props) => (props.width === "full" ? "100%" : "50%")}; - display: inline-block; - margin: 0.5rem 0; - white-space: nowrap; - text-overflow: ellipsis; - - a:hover, - a:focus { - text-decoration: underline; - color: var(--wally-red); - } -` - -const VersionSelect = styled.select` - &:hover { - cursor: pointer; - } - - &:hover, - &:focus { - text-decoration: underline; - color: var(--wally-red); - } - - > option { - color: var(--wally-mauve); - } -` - -const AuthorItem = styled.p` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -` - -const DependencyLinkWrapper = styled.div` - display: block; - position: relative; - width: 100%; - - &:hover { - > span { - visibility: visible; - } - } -` - -const DependencyLinkItem = styled.a` - display: block; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -` - -const DependencyLinkTooltip = styled.span` - visibility: hidden; - position: absolute; - z-index: 2; - color: white; - font-size: 0.8rem; - background-color: var(--wally-brown); - border-radius: 5px; - padding: 10px; - top: -45px; - left: 50%; - transform: translateX(-50%); - - &::before { - content: ""; - position: absolute; - transform: rotate(45deg); - background-color: var(--wally-brown); - padding: 6px; - z-index: 1; - top: 77%; - left: 45%; - } -` - -const StyledDownloadLink = styled.a` - display: inline-block; - height: 1rem; - width: auto; - padding-right: 0.3rem; - cursor: pointer; -` - -const MetaItem = ({ - title, - width, - children, -}: { - title: string - width?: WidthVariation - children: React.ReactNode -}) => { - return ( - - {title} - {children} - - ) -} - -const DependencyLink = ({ packageInfo }: { packageInfo: string }) => { - const packageMatch = packageInfo.match(/(.+\/.+)@[^\d]+([\d.]+)/) - if (packageMatch != null) { - const name = packageMatch[1] - const version = packageMatch[2] - return ( - - - {name + "@" + version} - - {name + "@" + version} - - ) - } - return {packageInfo} -} - -const DownloadLink = ({ - url, - filename, - children, -}: { - url: string - filename: string - children: React.ReactNode -}) => { - const handleAction = async () => { - // Using bare JavaScript mutations over a React ref keeps this link tag keyboard accessible, because you can't include an href on the base anchor tag and overwrite it with a ref, and we need an href to ensure full keyboard compatibility - const link = document.createElement("a") - - const result = await fetch(url, { - headers: { - "wally-version": "0.3.2", - }, - }) - - const blob = await result.blob() - const href = window.URL.createObjectURL(blob) - - link.href = href - link.download = filename - - document.body.appendChild(link) - - link.click() - - link.parentNode?.removeChild(link) - } - - return ( - <> - - {children} - - - ) -} - -const DownloadIcon = ({ packageName }: { packageName: string }) => ( - - Download a Wally Package - Downloads {packageName} - - - - - -) - -type PackageParams = { - packageScope: string - packageName: string -} - -export default function PackageView() { - const searchParams = useSearchParams() - const router = useRouter() - - const { packageScope, packageName } = useParams() - const [packageHistory, setPackageHistory] = useState<[WallyPackageMetadata]>() - const [packageVersion, setPackageVersion] = useState() - const [isLoaded, setIsLoaded] = useState(false) - const [isError, setIsError] = useState(false) - - const urlPackageVersion = searchParams.get("version") - if (urlPackageVersion != null && urlPackageVersion !== packageVersion) { - setPackageVersion(urlPackageVersion) - } - - const loadPackageData = async (packageScope: string, packageName: string) => { - const packageData = await getWallyPackageMetadata(packageScope, packageName) - - if (packageData == undefined) { - setIsError(true) - setIsLoaded(true) - return - } - - const filteredPackageData = packageData.versions.some( - (pack: WallyPackageMetadata) => !pack.package.version.includes("-") - ) - ? packageData.versions.filter( - (pack: WallyPackageMetadata) => !pack.package.version.includes("-") - ) - : packageData.versions - - setPackageHistory(filteredPackageData) - - if (urlPackageVersion == null) { - const latestVersion = filteredPackageData[0].package.version - setPackageVersion(latestVersion) - router.replace( - `/package/${packageScope}/${packageName}?version=${latestVersion}` - ) - } - - setIsLoaded(true) - } - - useEffect(() => { - loadPackageData(packageScope, packageName) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [packageScope, packageName]) - - if (!isLoaded) { - return ( - <> - - Loading... - - - ) - } - - if (isError) { - return ( - <> - - - - - ) - } - - const packageMetadata = packageHistory?.find( - (item: WallyPackageMetadata) => item.package.version === packageVersion - ) - - if (packageMetadata == undefined) { - return ( - <> - - {packageName} - - - Couldn't find {capitalize(packageName)} version{" "} - {packageVersion}. Are you sure that's a valid version? - - - - - - ) - } - - return ( - <> - - - - {packageName} - - - {packageMetadata.package.description ?? - `${capitalize(packageName)} has no provided description.`} - - - - - Metadata - - - - - - - { - router.push( - `/package/${packageScope}/${packageName}?version=${a.target.value}` - ) - }} - > - {packageHistory?.map((item: WallyPackageMetadata) => { - return ( - - ) - })} - - - - {packageMetadata.package.license && ( - - - {packageMetadata?.package.license} - - - )} - - - - - - - - - {capitalize(packageMetadata.package.realm)} - - - {/* TODO: Re-implement when Wally API supports custom source repos */} - {/* {packageMetadata?.package.registry && ( - - - {packageMetadata?.package.registry.replace("https://", "")} - - - )} */} - - {packageMetadata.package.authors.length > 0 && ( - - {packageMetadata.package.authors.map((author) => ( - {author} - ))} - - )} - - {Object.keys(packageMetadata.dependencies).length > 0 && ( - - {Object.values(packageMetadata.dependencies).map( - (dependency) => ( - - ) - )} - - )} - - {Object.keys(packageMetadata["server-dependencies"]).length > 0 && ( - - {Object.values(packageMetadata["server-dependencies"]).map( - (dependency) => ( - - ) - )} - - )} - - {Object.keys(packageMetadata["dev-dependencies"]).length > 0 && ( - - {Object.values(packageMetadata["dev-dependencies"]).map( - (dependency) => ( - - ) - )} - - )} - - - - - ) -}