diff --git a/app/components/Package/VersionDistribution.vue b/app/components/Package/VersionDistribution.vue index ae76102b4b..bb6e7e0295 100644 --- a/app/components/Package/VersionDistribution.vue +++ b/app/components/Package/VersionDistribution.vue @@ -9,6 +9,7 @@ import { drawNpmxLogoAndTaglineWatermark, } from '~/composables/useChartWatermark' import TooltipApp from '~/components/Tooltip/App.vue' +import { copyAltTextForVersionsBarChart } from '~/utils/charts' const props = defineProps<{ packageName: string @@ -16,6 +17,8 @@ const props = defineProps<{ }>() const { accentColors, selectedAccentColor } = useAccentColor() +const { copy, copied } = useClipboard() + const colorMode = useColorMode() const resolvedMode = shallowRef<'light' | 'dark'>('light') const rootEl = shallowRef(null) @@ -190,14 +193,14 @@ const chartConfig = computed(() => { fullscreen: false, table: false, tooltip: false, - altCopy: false, // TODO: set to true to enable the alt copy feature + altCopy: true, }, buttonTitles: { csv: $t('package.trends.download_file', { fileType: 'CSV' }), img: $t('package.trends.download_file', { fileType: 'PNG' }), svg: $t('package.trends.download_file', { fileType: 'SVG' }), annotator: $t('package.trends.toggle_annotator'), - altCopy: undefined, // TODO: set to proper translation key + altCopy: $t('package.trends.copy_alt.button_label'), // Do not make this text dependant on the `copied` variable, since this would re-render the component, which is undesirable if the minimap was used to select a time frame. }, callbacks: { img: args => { @@ -230,10 +233,19 @@ const chartConfig = computed(() => { loadFile(url, buildExportFilename('svg')) URL.revokeObjectURL(url) }, - // altCopy: ({ dataset: dst, config: cfg }: { dataset: Array; config: VueUiXyConfig}) => { - // // TODO: implement a reusable copy-alt-text-to-clipboard feature based on the dataset & configuration - // console.log({ dst, cfg}) - // } + altCopy: ({ dataset: dst, config: cfg }) => + copyAltTextForVersionsBarChart({ + dataset: dst, + config: { + ...cfg, + datapointLabels: xAxisLabels.value, + dateRangeLabel: dateRangeLabel.value, + semverGroupingMode: groupingMode.value, + copy, + $t, + numberFormatter: compactNumberFormatter.value.format, + }, + }), }, }, grid: { @@ -575,6 +587,16 @@ const chartConfig = computed(() => { aria-hidden="true" /> + diff --git a/app/utils/charts.ts b/app/utils/charts.ts index 84e071ede0..bb253632d5 100644 --- a/app/utils/charts.ts +++ b/app/utils/charts.ts @@ -1,4 +1,9 @@ -import type { AltCopyArgs, VueUiXyConfig, VueUiXyDatasetLineItem } from 'vue-data-ui' +import type { + AltCopyArgs, + VueUiXyConfig, + VueUiXyDatasetBarItem, + VueUiXyDatasetLineItem, +} from 'vue-data-ui' import type { ChartTimeGranularity } from '~/types/chart' export function sum(numbers: number[]): number { @@ -413,6 +418,11 @@ export type TrendLineDataset = { [key: string]: unknown } | null +export type VersionsBarDataset = { + bars: VueUiXyDatasetBarItem[] + [key: string]: unknown +} | null + export type TrendTranslateKey = number | 'package.trends.y_axis_label' | (string & {}) export type TrendTranslateFunction = { @@ -431,6 +441,12 @@ export type TrendLineConfig = VueUiXyConfig & { numberFormatter: (value: number) => string } +export type VersionsBarConfig = Omit< + TrendLineConfig, + 'formattedDates' | 'hasEstimation' | 'formattedDatasetValues' | 'granularity' +> & { datapointLabels: string[]; dateRangeLabel: string; semverGroupingMode: string } + +// Used for TrendsChart.vue export function createAltTextForTrendLineChart({ dataset, config, @@ -519,3 +535,63 @@ export async function copyAltTextForTrendLineChart({ const altText = createAltTextForTrendLineChart({ dataset, config }) await config.copy(altText) } + +// Used for VersionDistribution.vue +export function createAltTextForVersionsBarChart({ + dataset, + config, +}: AltCopyArgs) { + if (!dataset) return '' + + const series = dataset.bars[0]?.series ?? [] + const versions = series.map((value, index) => ({ + index, + name: config.datapointLabels[index] ?? '-', + rawDownloads: value ?? 0, + downloads: config.numberFormatter(value ?? 0), + })) + + const versionWithMaxDownloads = + versions.length > 0 + ? versions.reduce((max, current) => (current.rawDownloads > max.rawDownloads ? current : max)) + : undefined + + const per_version_analysis = versions + .toReversed() + .filter(v => v.index !== versionWithMaxDownloads?.index) + .map(v => + config.$t(`package.versions.copy_alt.per_version_analysis`, { + version: v?.name ?? '-', + downloads: v?.downloads ?? '-', + }), + ) + .join(', ') + + const semver_grouping_mode = + config.semverGroupingMode === 'major' + ? config.$t('package.versions.grouping_major') + : config.$t('package.versions.grouping_minor') + + const altText = `${config.$t('package.versions.copy_alt.general_description', { + package_name: dataset?.bars[0]?.name ?? '-', + versions_count: versions?.length, + semver_grouping_mode: semver_grouping_mode.toLocaleLowerCase(), + first_version: versions[0]?.name ?? '-', + last_version: versions.at(-1)?.name ?? '-', + date_range_label: config.dateRangeLabel ?? '-', + max_downloaded_version: versionWithMaxDownloads?.name ?? '-', + max_version_downloads: versionWithMaxDownloads?.downloads ?? '-', + per_version_analysis, + watermark: config.$t('package.trends.copy_alt.watermark'), + })}` + + return altText +} + +export async function copyAltTextForVersionsBarChart({ + dataset, + config, +}: AltCopyArgs) { + const altText = createAltTextForVersionsBarChart({ dataset, config }) + await config.copy(altText) +} diff --git a/i18n/locales/en.json b/i18n/locales/en.json index b7a14fe3ec..3e4354e485 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -312,7 +312,11 @@ "filter_help": "Semver range filter help", "filter_tooltip": "Filter versions using a {link}. For example, ^3.0.0 shows all 3.x versions.", "filter_tooltip_link": "semver range", - "no_matches": "No versions match this range" + "no_matches": "No versions match this range", + "copy_alt": { + "per_version_analysis": "{version} version was downloaded {downloads} times", + "general_description": "Bar chart showing per-version downloads for {versions_count} {semver_grouping_mode} versions of the {package_name} package, {date_range_label} from the {first_version} version to the {last_version} version. The most downloaded version is {max_downloaded_version} with {max_version_downloads} downloads. {per_version_analysis} {watermark}." + } }, "dependencies": { "title": "Dependency ({count}) | Dependencies ({count})", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 1a62055023..01cfefe21e 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -312,7 +312,11 @@ "filter_help": "Infos sur le filtre de plage semver", "filter_tooltip": "Filtrer les versions avec une {link}. Par exemple, ^3.0.0 affiche toutes les versions 3.x.", "filter_tooltip_link": "plage semver", - "no_matches": "Aucune version ne correspond à cette plage" + "no_matches": "Aucune version ne correspond à cette plage", + "copy_alt": { + "per_version_analysis": "La version {version} a été téléchargée {downloads} fois", + "general_description": "Graphique en barres montrant les téléchargements par version pour {versions_count} versions {semver_grouping_mode} du paquet {package_name}, {date_range_label} de la version {first_version} à la version {last_version}. La version la plus téléchargée est {max_downloaded_version} avec {max_version_downloads} téléchargements. {per_version_analysis} {watermark}." + } }, "dependencies": { "title": "Dépendances ({count})", diff --git a/i18n/schema.json b/i18n/schema.json index ce915ed5ec..aa7681bc31 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -942,6 +942,18 @@ }, "no_matches": { "type": "string" + }, + "copy_alt": { + "type": "object", + "properties": { + "per_version_analysis": { + "type": "string" + }, + "general_description": { + "type": "string" + } + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/lunaria/files/en-GB.json b/lunaria/files/en-GB.json index 12789651b4..4ca9ba59df 100644 --- a/lunaria/files/en-GB.json +++ b/lunaria/files/en-GB.json @@ -311,7 +311,11 @@ "filter_help": "Semver range filter help", "filter_tooltip": "Filter versions using a {link}. For example, ^3.0.0 shows all 3.x versions.", "filter_tooltip_link": "semver range", - "no_matches": "No versions match this range" + "no_matches": "No versions match this range", + "copy_alt": { + "per_version_analysis": "{version} version was downloaded {downloads} times", + "general_description": "Bar chart showing per-version downloads for {versions_count} {semver_grouping_mode} versions of the {package_name} package, {date_range_label} from the {first_version} version to the {last_version} version. The most downloaded version is {max_downloaded_version} with {max_version_downloads} downloads. {per_version_analysis} {watermark}." + } }, "dependencies": { "title": "Dependency ({count}) | Dependencies ({count})", diff --git a/lunaria/files/en-US.json b/lunaria/files/en-US.json index 7b621707ec..69a66c3f7f 100644 --- a/lunaria/files/en-US.json +++ b/lunaria/files/en-US.json @@ -311,7 +311,11 @@ "filter_help": "Semver range filter help", "filter_tooltip": "Filter versions using a {link}. For example, ^3.0.0 shows all 3.x versions.", "filter_tooltip_link": "semver range", - "no_matches": "No versions match this range" + "no_matches": "No versions match this range", + "copy_alt": { + "per_version_analysis": "{version} version was downloaded {downloads} times", + "general_description": "Bar chart showing per-version downloads for {versions_count} {semver_grouping_mode} versions of the {package_name} package, {date_range_label} from the {first_version} version to the {last_version} version. The most downloaded version is {max_downloaded_version} with {max_version_downloads} downloads. {per_version_analysis} {watermark}." + } }, "dependencies": { "title": "Dependency ({count}) | Dependencies ({count})", diff --git a/lunaria/files/fr-FR.json b/lunaria/files/fr-FR.json index 4ecef381b0..c1ed3272c5 100644 --- a/lunaria/files/fr-FR.json +++ b/lunaria/files/fr-FR.json @@ -311,7 +311,11 @@ "filter_help": "Infos sur le filtre de plage semver", "filter_tooltip": "Filtrer les versions avec une {link}. Par exemple, ^3.0.0 affiche toutes les versions 3.x.", "filter_tooltip_link": "plage semver", - "no_matches": "Aucune version ne correspond à cette plage" + "no_matches": "Aucune version ne correspond à cette plage", + "copy_alt": { + "per_version_analysis": "La version {version} a été téléchargée {downloads} fois", + "general_description": "Graphique en barres montrant les téléchargements par version pour {versions_count} versions {semver_grouping_mode} du paquet {package_name}, {date_range_label} de la version {first_version} à la version {last_version}. La version la plus téléchargée est {max_downloaded_version} avec {max_version_downloads} téléchargements. {per_version_analysis} {watermark}." + } }, "dependencies": { "title": "Dépendances ({count})",