-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add publish dates under each package in the diff (#909)
* Add publish dates under each package in the diff * Refactor publish date code, prepare for dates in autocomplete * Show time on hover * Show publication dates in autocomplete * Print dates in the right timezone * Fix type error
- Loading branch information
Showing
11 changed files
with
209 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { type ComponentPropsWithoutRef } from "react"; | ||
import ClientDate from "^/components/ClientDate"; | ||
import Skeleton from "^/components/ui/Skeleton"; | ||
import getVersionData from "^/lib/api/npm/getVersionData"; | ||
import { cx } from "^/lib/cva"; | ||
import type SimplePackageSpec from "^/lib/SimplePackageSpec"; | ||
import suspense from "^/lib/suspense"; | ||
|
||
export interface PublishDateProps extends ComponentPropsWithoutRef<"div"> { | ||
pkg: SimplePackageSpec; | ||
} | ||
|
||
const shared = cx("my-1 flex h-5 items-center justify-center"); | ||
|
||
async function PublishDate({ pkg, className, ...props }: PublishDateProps) { | ||
const versionData = await getVersionData(pkg); | ||
|
||
const time = versionData[pkg.version]?.time ?? null; | ||
|
||
return ( | ||
<ClientDate | ||
time={time} | ||
className={cx(shared, "cursor-help", className)} | ||
{...props} | ||
/> | ||
); | ||
} | ||
|
||
function PublishDateFallback({ className }: PublishDateProps) { | ||
return ( | ||
<div className={cx(shared, className)}> | ||
<Skeleton className="h-2 w-16" /> | ||
</div> | ||
); | ||
} | ||
|
||
const SuspensedPublishDate = suspense(PublishDate, PublishDateFallback); | ||
|
||
export default SuspensedPublishDate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
import { type VersionData } from "^/lib/api/npm/getVersionData"; | ||
|
||
export const VERSIONS_PARAMETER_PACKAGE = "package"; | ||
export type Version = { version: string; tags?: string[] }; | ||
export interface Version extends VersionData { | ||
version: string; | ||
} | ||
export type SpecsEndpointResponse = Version[]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
"use client"; | ||
|
||
import { type HTMLProps, memo } from "react"; | ||
import Tooltip from "./ui/Tooltip"; | ||
|
||
export interface ClientDateProps extends HTMLProps<HTMLDivElement> { | ||
time: string; | ||
} | ||
|
||
const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
|
||
/** | ||
* Prints dates in the clients timezone, but also shows the UTC time in a tooltip. | ||
*/ | ||
const ClientDateInner = ({ time, ...props }: ClientDateProps) => { | ||
const date = new Date(time); | ||
|
||
const label = ( | ||
<div className="flex flex-col gap-2"> | ||
<div> | ||
{date.toLocaleString()}{" "} | ||
<span className="opacity-30">({currentTimezone})</span> | ||
</div> | ||
<div> | ||
{date.toLocaleString(undefined, { | ||
timeZone: "UTC", | ||
})}{" "} | ||
<span className="opacity-30">(UTC)</span> | ||
</div> | ||
</div> | ||
); | ||
|
||
return ( | ||
<Tooltip label={label}> | ||
<span {...props}>{date.toLocaleDateString(undefined)}</span> | ||
</Tooltip> | ||
); | ||
}; | ||
|
||
const ClientDate = memo(ClientDateInner); | ||
|
||
export default ClientDate; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { unstable_cache } from "next/cache"; | ||
import { cache } from "react"; | ||
import type SimplePackageSpec from "^/lib/SimplePackageSpec"; | ||
import { simplePackageSpecToString } from "^/lib/SimplePackageSpec"; | ||
import packument from "./packument"; | ||
|
||
// Packuments include a lot of data, often enough to make them too large for the cache. | ||
// For our diff page, and autocomplete, we want to find versions and which date and tag they have. | ||
// So we build this data here, and run it throug the cache to cache this value. | ||
|
||
export type VersionData = { | ||
time: string; | ||
tags?: string[]; | ||
}; | ||
|
||
export type VersionMap = { | ||
[version: string]: VersionData; | ||
}; | ||
|
||
async function getVersionDataInner( | ||
spec: string | SimplePackageSpec, | ||
): Promise<VersionMap> { | ||
const specString = | ||
typeof spec === "string" ? spec : simplePackageSpecToString(spec); | ||
|
||
const { | ||
time, | ||
"dist-tags": tags, | ||
versions, | ||
} = await packument(specString, { | ||
// Response is too large to cache | ||
next: { revalidate: false }, | ||
}); | ||
|
||
const versionData: VersionMap = {}; | ||
|
||
for (const [version, data] of Object.entries(versions)) { | ||
versionData[version] = { time: time[version] }; | ||
} | ||
|
||
for (const [tag, version] of Object.entries(tags)) { | ||
const entry = versionData[version]; | ||
if (entry) { | ||
if (entry.tags != null) { | ||
entry.tags.push(tag); | ||
} else { | ||
entry.tags = [tag]; | ||
} | ||
} | ||
} | ||
|
||
return versionData; | ||
} | ||
|
||
const getVersionData = | ||
// Cache for request de-dupe | ||
cache( | ||
// unstable cache to cache between requests | ||
unstable_cache(getVersionDataInner, ["versionData"]), | ||
); | ||
|
||
export default getVersionData; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { | ||
type ComponentType, | ||
type FunctionComponent, | ||
type ReactElement, | ||
type ReactNode, | ||
Suspense as SuspenseComp, | ||
} from "react"; | ||
|
||
/** | ||
* Wrap a component in a Suspense component. | ||
*/ | ||
export default function suspense<T>( | ||
WrappedComponent: ComponentType<T>, | ||
fallback: FunctionComponent<T> | ReactNode = <></>, | ||
): FunctionComponent<T> { | ||
const c = (props: T): ReactElement => ( | ||
<SuspenseComp | ||
fallback={ | ||
typeof fallback === "function" ? fallback(props) : fallback | ||
} | ||
> | ||
<WrappedComponent {...(props as any)} /> | ||
</SuspenseComp> | ||
); | ||
|
||
c.displayName = `suspense(${ | ||
WrappedComponent.displayName ?? WrappedComponent.name | ||
})`; | ||
|
||
return c; | ||
} |