Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,6 @@ Made with ❤️

Published under [MIT License](./LICENSE).

## Star History

<a href="https://www.star-history.com/?repos=npmx-dev%2Fnpmx.dev&type=date&legend=top-left">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=npmx-dev/npmx.dev&type=date&theme=dark&legend=top-left&sealed_token=1RI8ZdgQL3NncQeMm3sTwgYPL_7_ELdIGe5yiswir2iZipZwBtsYd4Y6zkO2fZLUP3wRcDN38GLKA4B1b3JE3pxEg7x_w8uOivwT3rL8Jcr-866HKSRMpjqmEwPavKp57ZdTyo04KHRbKthXVe360AJhcjmbXWxKVO9EDFh995ssNIH32hNArVB4MwAm" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=npmx-dev/npmx.dev&type=date&legend=top-left&sealed_token=1RI8ZdgQL3NncQeMm3sTwgYPL_7_ELdIGe5yiswir2iZipZwBtsYd4Y6zkO2fZLUP3wRcDN38GLKA4B1b3JE3pxEg7x_w8uOivwT3rL8Jcr-866HKSRMpjqmEwPavKp57ZdTyo04KHRbKthXVe360AJhcjmbXWxKVO9EDFh995ssNIH32hNArVB4MwAm" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=npmx-dev/npmx.dev&type=date&legend=top-left&sealed_token=1RI8ZdgQL3NncQeMm3sTwgYPL_7_ELdIGe5yiswir2iZipZwBtsYd4Y6zkO2fZLUP3wRcDN38GLKA4B1b3JE3pxEg7x_w8uOivwT3rL8Jcr-866HKSRMpjqmEwPavKp57ZdTyo04KHRbKthXVe360AJhcjmbXWxKVO9EDFh995ssNIH32hNArVB4MwAm" />
</picture>
</a>

## Sponsors

<a href="https://opencollective.com/npmx" target="_blank" rel="noopener noreferrer">
Expand Down
101 changes: 45 additions & 56 deletions app/components/Package/TimelineChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
import type { TimelineVersion, SubEvent } from '~~/server/api/registry/timeline/[...pkg].get'
import { drawSmallNpmxLogoAndTaglineWatermark } from '~/composables/useChartWatermark'
import { useColors } from '~/composables/useColors'
import { parseStableVersion } from '~/utils/versions'
import { downloadFileLink } from '~/utils/download'
import { useCopyChartPng } from '~/composables/useCopyChartPng'
import { useElementSize, useTimeoutFn } from '@vueuse/core'
Expand Down Expand Up @@ -111,44 +110,8 @@ const convertedData = computed(() => {
return addEvaluationFlags(entries, props.versionSubEvents).toReversed()
})

type StableVersion = {
major: number
minor: number
patch: number
}

const orderedConvertedData = computed(() => {
if (!settings.value.timelineChart.isOrdered) {
return convertedData.value
}

// Hide pre-releases and reorder stable versions semantically
return convertedData.value
.map(entry => ({
entry,
parsedVersion: parseStableVersion(entry.version),
}))
.filter(
(
item,
): item is { entry: (typeof convertedData.value)[number]; parsedVersion: StableVersion } => {
return item.parsedVersion !== null
},
)
.toSorted((a, b) => {
if (a.parsedVersion.major !== b.parsedVersion.major) {
return a.parsedVersion.major - b.parsedVersion.major
}
if (a.parsedVersion.minor !== b.parsedVersion.minor) {
return a.parsedVersion.minor - b.parsedVersion.minor
}
return a.parsedVersion.patch - b.parsedVersion.patch
})
.map(item => item.entry)
})

watch(
orderedConvertedData,
convertedData,
async () => {
await nextTick()
const chart = chartRef.value
Expand All @@ -158,15 +121,15 @@ watch(
{ flush: 'post' },
)

const versions = computed(() => orderedConvertedData.value.map(d => d.version))
const versions = computed(() => convertedData.value.map(d => d.version))

const activeVersionIndex = computed(() => {
if (!activeVersion.value) return -1
return versions.value.findIndex(v => v === activeVersion.value)
})

const seriesTotalSize = computed(() => {
const values = orderedConvertedData.value.map(d => d.totalSize)
const values = convertedData.value.map(d => d.totalSize)
if (!values.length) {
return { values, min: 0, max: 0 }
}
Expand All @@ -178,7 +141,7 @@ const seriesTotalSize = computed(() => {
})

const seriesDependencies = computed(() => {
const values = orderedConvertedData.value.map(d => d.dependencyCount)
const values = convertedData.value.map(d => d.dependencyCount)
if (!values.length) {
return { values, min: 0, max: 0 }
}
Expand Down Expand Up @@ -250,7 +213,7 @@ interface DependencySegment {
// significantly sized dependency is a segment, the package itself is a segment,
// and the rest is a segment ("Other").
const dependencySegments = computed<DependencySegment[]>(() => {
const data = orderedConvertedData.value
const data = convertedData.value
const reference = data.at(-1)
if (!reference) return []

Expand Down Expand Up @@ -338,7 +301,7 @@ const datasets = computed<{
? undefined
: E18E_GRADIENT_COLORS,
color: colors.value.fgSubtle,
source: orderedConvertedData.value,
source: convertedData.value,
},
],
dependencyCount: [
Expand All @@ -351,7 +314,7 @@ const datasets = computed<{
? undefined
: E18E_GRADIENT_COLORS,
color: colors.value.fgSubtle,
source: orderedConvertedData.value,
source: convertedData.value,
},
],
}
Expand Down Expand Up @@ -577,7 +540,7 @@ const config = computed<VueUiXyConfig>(() => {
svg: commonConfig.value.callbacks!.svg,
altCopy: () =>
copyAltTextForTimelineChart({
dataset: orderedConvertedData.value,
dataset: convertedData.value,
config: {
packageName: packageName.value,
metric: activeTab.value,
Expand Down Expand Up @@ -747,7 +710,7 @@ function getErrorDatapointPlots(

const indexSelection = computed(() => {
if (props.selectedVersion == null) return null
return orderedConvertedData.value.findIndex(v => v.version === props.selectedVersion)
return convertedData.value.findIndex(v => v.version === props.selectedVersion)
})

const stackbarConfig = computed<VueUiStackbarConfig>(() => ({
Expand Down Expand Up @@ -871,9 +834,20 @@ const stackbarConfig = computed<VueUiStackbarConfig>(() => ({
function stackbarTooltipTime(datapoint: VueUiStackbarTooltipDatapoint[]): string | undefined {
const absoluteIndex = datapoint[0]?.timeLabel?.absoluteIndex
if (absoluteIndex == null) return undefined
return orderedConvertedData.value[absoluteIndex]?.time
return convertedData.value[absoluteIndex]?.time
}

// Sort order shared with the page + list via the query string (default: publish time)
const sort = usePermalink<'time' | 'semver'>('sort', 'semver')

// "Stable only" filter shared with the page + list via the query string.
const stableOnly = useTimelineStableOnly()

const timelineSortOptions = computed(() => [
{ value: 'time' as const, label: $t('package.timeline.chart.sort_time') },
{ value: 'semver' as const, label: $t('package.timeline.chart.sort_semver') },
])

const timelineMetricTabs = computed(() => [
{
value: 'totalSize' as const,
Expand All @@ -900,7 +874,7 @@ const timelineMetricTabs = computed(() => [
:class="{ loading: shouldPauseChartAnimations || loading }"
id="timeline-chart"
>
<div class="mt-4 flex flex-row flex-wrap items-center justify-between gap-4">
<div class="my-2 flex flex-row flex-wrap items-center justify-between gap-4">
<div class="w-full sm:w-auto">
<label for="timeline-chart-metric" class="sr-only">
{{ $t('package.timeline.chart.tab_aria_label') }}
Expand Down Expand Up @@ -933,9 +907,23 @@ const timelineMetricTabs = computed(() => [
</div>
</div>

<div class="flex flex-row flex-wrap gap-4">
<div class="flex flex-row flex-wrap items-center gap-4">
<div class="flex items-center gap-2">
<label for="timeline-chart-sort" class="text-sm text-fg-subtle">
{{ $t('package.timeline.chart.sort_label') }}
</label>
<select
id="timeline-chart-sort"
v-model="sort"
class="block w-fit rounded-md border border-border bg-bg px-3 py-2 text-sm text-fg"
>
<option v-for="option in timelineSortOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</select>
</div>
<SettingsToggle
v-model="settings.timelineChart.isOrdered"
v-model="stableOnly"
:label="$t('package.timeline.chart.ordered_versions')"
/>
<template v-if="activeTab === 'totalSize' || activeTab === 'dependencyCount'">
Expand All @@ -961,20 +949,21 @@ const timelineMetricTabs = computed(() => [
<!-- Custom tooltip -->
<template #tooltip="{ timeLabel }">
<TimelineChartXyTooltip
v-if="convertedData[timeLabel.absoluteIndex]"
:timeLabel
:version="orderedConvertedData[timeLabel.absoluteIndex]?.version"
:tags="orderedConvertedData[timeLabel.absoluteIndex]?.tags"
:datetime="orderedConvertedData[timeLabel.absoluteIndex]?.time!"
:version="convertedData[timeLabel.absoluteIndex]?.version"
:tags="convertedData[timeLabel.absoluteIndex]?.tags"
:datetime="convertedData[timeLabel.absoluteIndex]?.time!"
:activeTab
:totalSize="
bytesFormatter.format(orderedConvertedData[timeLabel.absoluteIndex]?.totalSize ?? 0)
bytesFormatter.format(convertedData[timeLabel.absoluteIndex]?.totalSize ?? 0)
"
:dependencyCount="
compactNumberFormatter.format(
orderedConvertedData[timeLabel.absoluteIndex]?.dependencyCount ?? 0,
convertedData[timeLabel.absoluteIndex]?.dependencyCount ?? 0,
)
"
:events="orderedConvertedData[timeLabel.absoluteIndex]?.events"
:events="convertedData[timeLabel.absoluteIndex]?.events"
/>
</template>

Expand Down
3 changes: 2 additions & 1 deletion app/components/Package/TrendsChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2065,7 +2065,8 @@ const copyEmbedUrl = () => copyEmbed(embedUrl.value)
background: var(--bg-elevated) !important;
}

.vue-ui-pen-and-paper-action {
.vue-ui-pen-and-paper-action,
.vue-ui-pen-and-paper-drag-handle {
background: var(--bg-elevated) !important;
border: none !important;
}
Expand Down
2 changes: 0 additions & 2 deletions app/composables/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export interface AppSettings {
timelineChart: {
isZeroBased: boolean
showZoom: boolean
isOrdered: boolean
}
}

Expand Down Expand Up @@ -92,7 +91,6 @@ const DEFAULT_SETTINGS: AppSettings = {
timelineChart: {
isZeroBased: false,
showZoom: false,
isOrdered: true,
},
}

Expand Down
35 changes: 35 additions & 0 deletions app/composables/useTimelineStableOnly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useRouteQuery } from '@vueuse/router'
import { parseStableVersion } from '~/utils/versions'

/**
* "Stable only" filter for the package timeline, persisted in the query string
* (`?stable-only=true` / `?stable-only=false`).
*
* The default is version-derived: off when the currently selected version is a
* pre-release (so the version you navigated to stays visible), on otherwise.
* The value equal to that default is omitted from the URL to keep links clean.
*
* Shared between the timeline page (list + sub-events) and the chart (toggle),
* both of which read the same route query so they stay in sync.
*/
export function useTimelineStableOnly(): WritableComputedRef<boolean> {
const route = useRoute('timeline')
const stableParam = useRouteQuery<string | undefined>('stable-only', undefined)

const selectedIsUnstable = computed(() => {
const selected = route.params.version as string | undefined
return !!selected && parseStableVersion(selected) === null
})

return computed<boolean>({
get() {
if (stableParam.value === 'true') return true
if (stableParam.value === 'false') return false
return !selectedIsUnstable.value
},
set(value) {
const isDefault = value === !selectedIsUnstable.value
stableParam.value = isDefault ? undefined : value ? 'true' : 'false'
},
})
}
Loading
Loading