Skip to content
Merged
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
224 changes: 118 additions & 106 deletions app/components/PackageDownloadAnalytics.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ref, computed, shallowRef, watch } from 'vue'
import type { VueUiXyDatasetItem } from 'vue-data-ui'
import { VueUiXy } from 'vue-data-ui/vue-ui-xy'
import { useDebounceFn, useElementSize } from '@vueuse/core'
import { useCssVariables } from '../composables/useColors'
import { OKLCH_NEUTRAL_FALLBACK, transparentizeOklch } from '../utils/colors'

const {
weeklyDownloads,
Expand All @@ -24,9 +26,19 @@ const rootEl = shallowRef<HTMLElement | null>(null)
const { width } = useElementSize(rootEl)

onMounted(() => {
rootEl.value = document.documentElement
resolvedMode.value = colorMode.value === 'dark' ? 'dark' : 'light'
})

const { colors } = useCssVariables(
['--bg', '--bg-subtle', '--bg-elevated', '--fg-subtle', '--border', '--border-subtle'],
{
element: rootEl,
watchHtmlAttributes: true,
watchResize: false, // set to true only if a var changes color on resize
},
)

watch(
() => colorMode.value,
value => {
Expand All @@ -49,7 +61,9 @@ const accentColorValueById = computed<Record<string, string>>(() => {

const accent = computed(() => {
const id = selectedAccentColor.value
return id ? (oklchToHex(accentColorValueById.value[id]!) ?? '#8A8A8A') : '#8A8A8A'
return id
? (accentColorValueById.value[id] ?? colors.value.fgSubtle ?? OKLCH_NEUTRAL_FALLBACK)
: (colors.value.fgSubtle ?? OKLCH_NEUTRAL_FALLBACK)
})

const mobileBreakpointWidth = 640
Expand All @@ -58,10 +72,6 @@ const isMobile = computed(() => {
return width.value > 0 && width.value < mobileBreakpointWidth
})

onMounted(() => {
rootEl.value = document.documentElement
})

type ChartTimeGranularity = 'daily' | 'weekly' | 'monthly' | 'yearly'
type EvolutionData =
| DailyDownloadPoint[]
Expand Down Expand Up @@ -444,119 +454,121 @@ const loadFile = (link: string, filename: string) => {
a.remove()
}

const config = computed(() => ({
theme: isDarkMode.value ? 'dark' : 'default',
chart: {
height: isMobile.value ? 850 : 600,
userOptions: {
buttons: {
pdf: false,
labels: false,
fullscreen: false,
table: false,
tooltip: false,
},
callbacks: {
img: ({ imageUri }: { imageUri: string }) => {
loadFile(
imageUri,
`${packageName}-${selectedGranularity.value}_${startDate.value}_${endDate.value}.png`,
)
},
csv: (csvStr: string) => {
const blob = new Blob([csvStr.replace('data:text/csv;charset=utf-8,', '')])
const url = URL.createObjectURL(blob)
loadFile(
url,
`${packageName}-${selectedGranularity.value}_${startDate.value}_${endDate.value}.csv`,
)
URL.revokeObjectURL(url)
const config = computed(() => {
return {
theme: isDarkMode.value ? 'dark' : 'default',
chart: {
height: isMobile.value ? 850 : 600,
userOptions: {
buttons: {
pdf: false,
labels: false,
fullscreen: false,
table: false,
tooltip: false,
},
svg: ({ blob }: { blob: Blob }) => {
const url = URL.createObjectURL(blob)
loadFile(
url,
`${packageName}-${selectedGranularity.value}_${startDate.value}_${endDate.value}.svg`,
)
URL.revokeObjectURL(url)
callbacks: {
img: ({ imageUri }: { imageUri: string }) => {
loadFile(
imageUri,
`${packageName}-${selectedGranularity.value}_${startDate.value}_${endDate.value}.png`,
)
},
csv: (csvStr: string) => {
const blob = new Blob([csvStr.replace('data:text/csv;charset=utf-8,', '')])
const url = URL.createObjectURL(blob)
loadFile(
url,
`${packageName}-${selectedGranularity.value}_${startDate.value}_${endDate.value}.csv`,
)
URL.revokeObjectURL(url)
},
svg: ({ blob }: { blob: Blob }) => {
const url = URL.createObjectURL(blob)
loadFile(
url,
`${packageName}-${selectedGranularity.value}_${startDate.value}_${endDate.value}.svg`,
)
URL.revokeObjectURL(url)
},
},
},
},
backgroundColor: isDarkMode.value ? '#0A0A0A' : '#FFFFFF',
grid: {
stroke: isDarkMode.value ? '#4A4A4A' : '#a3a3a3',
labels: {
axis: {
yLabel: $t('package.downloads.y_axis_label', {
granularity: $t(`package.downloads.granularity_${selectedGranularity.value}`),
}),
xLabel: packageName,
yLabelOffsetX: 12,
fontSize: 24,
},
xAxisLabels: {
values: chartData.value?.dates,
showOnlyAtModulo: true,
modulo: 12,
},
yAxis: {
formatter,
useNiceScale: true,
backgroundColor: colors.value.bg,
grid: {
stroke: colors.value.border,
labels: {
axis: {
yLabel: $t('package.downloads.y_axis_label', {
granularity: $t(`package.downloads.granularity_${selectedGranularity.value}`),
}),
xLabel: packageName,
yLabelOffsetX: 12,
fontSize: 24,
},
xAxisLabels: {
values: chartData.value?.dates,
showOnlyAtModulo: true,
modulo: 12,
},
yAxis: {
formatter,
useNiceScale: true,
},
},
},
},
highlighter: {
useLine: true,
},
legend: {
show: false, // As long as a single package is displayed
},
tooltip: {
borderColor: 'transparent',
backdropFilter: false,
backgroundColor: 'transparent',
customFormat: ({
absoluteIndex,
datapoint,
}: {
absoluteIndex: number
datapoint: Record<string, any>
}) => {
if (!datapoint) return ''
const displayValue = formatter({ value: datapoint[0]?.value ?? 0 })
return `<div class="flex flex-col font-mono text-xs p-3 border border-border rounded-md bg-white/10 dark:bg-[#0A0A0A]/10 backdrop-blur-md">
highlighter: {
useLine: true,
},
legend: {
show: false, // As long as a single package is displayed
},
tooltip: {
borderColor: 'transparent',
backdropFilter: false,
backgroundColor: 'transparent',
customFormat: ({
absoluteIndex,
datapoint,
}: {
absoluteIndex: number
datapoint: Record<string, any>
}) => {
if (!datapoint) return ''
const displayValue = formatter({ value: datapoint[0]?.value ?? 0 })
return `<div class="flex flex-col font-mono text-xs p-3 border border-border rounded-md bg-[var(--bg)]/10 backdrop-blur-md">
<span class="text-fg-subtle">${chartData.value?.dates[absoluteIndex]}</span>
<span class="text-xl">${displayValue}</span>
</div>
`
},
},
},
zoom: {
maxWidth: 500,
customFormat:
displayedGranularity.value !== 'weekly'
? undefined
: ({ absoluteIndex, side }: { absoluteIndex: number; side: 'left' | 'right' }) => {
const parts = extractDates(chartData.value.dates[absoluteIndex] ?? '')
return side === 'left' ? parts[0] : parts.at(-1)
},
highlightColor: isDarkMode.value ? '#2A2A2A' : '#E1E5E8',
minimap: {
show: true,
lineColor: '#FAFAFA',
selectedColor: accent.value,
selectedColorOpacity: 0.06,
frameColor: isDarkMode.value ? '#3A3A3A' : '#a3a3a3',
},
preview: {
fill: accent.value + 10,
stroke: accent.value + 60,
strokeWidth: 1,
strokeDasharray: 3,
zoom: {
maxWidth: 500,
customFormat:
displayedGranularity.value !== 'weekly'
? undefined
: ({ absoluteIndex, side }: { absoluteIndex: number; side: 'left' | 'right' }) => {
const parts = extractDates(chartData.value.dates[absoluteIndex] ?? '')
return side === 'left' ? parts[0] : parts.at(-1)
},
highlightColor: colors.value.bgElevated,
minimap: {
show: true,
lineColor: '#FAFAFA',
selectedColor: accent.value,
selectedColorOpacity: 0.06,
frameColor: colors.value.border,
},
preview: {
fill: transparentizeOklch(accent.value, isDarkMode.value ? 0.95 : 0.92),
stroke: transparentizeOklch(accent.value, 0.5),
strokeWidth: 1,
strokeDasharray: 3,
},
},
},
},
}))
}
})
</script>

<template>
Expand Down
43 changes: 34 additions & 9 deletions app/components/PackageWeeklyDownloadStats.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { VueUiSparkline } from 'vue-data-ui/vue-ui-sparkline'
import { useCssVariables } from '../composables/useColors'
import { OKLCH_NEUTRAL_FALLBACK, lightenOklch } from '../utils/colors'

const { packageName } = defineProps<{
packageName: string
Expand All @@ -19,7 +21,10 @@ const colorMode = useColorMode()

const resolvedMode = ref<'light' | 'dark'>('light')

const rootEl = shallowRef<HTMLElement | null>(null)

onMounted(() => {
rootEl.value = document.documentElement
resolvedMode.value = colorMode.value === 'dark' ? 'dark' : 'light'
})

Expand All @@ -31,6 +36,24 @@ watch(
{ flush: 'sync' },
)

const { colors } = useCssVariables(
[
'--bg',
'--fg',
'--bg-subtle',
'--bg-elevated',
'--border-hover',
'--fg-subtle',
'--border',
'--border-subtle',
],
{
element: rootEl,
watchHtmlAttributes: true,
watchResize: false, // set to true only if a var changes color on resize
},
)

const isDarkMode = computed(() => resolvedMode.value === 'dark')

const accentColorValueById = computed<Record<string, string>>(() => {
Expand All @@ -43,14 +66,16 @@ const accentColorValueById = computed<Record<string, string>>(() => {

const accent = computed(() => {
const id = selectedAccentColor.value
return id ? (oklchToHex(accentColorValueById.value[id]!) ?? '#8A8A8A') : '#8A8A8A'
return id
? (accentColorValueById.value[id] ?? colors.value.fgSubtle ?? OKLCH_NEUTRAL_FALLBACK)
: (colors.value.fgSubtle ?? OKLCH_NEUTRAL_FALLBACK)
})

const pulseColor = computed(() => {
if (!selectedAccentColor.value) {
return isDarkMode.value ? '#BFBFBF' : '#E0E0E0'
return colors.value.fgSubtle
}
return isDarkMode.value ? accent.value : lightenHex(accent.value, 0.5)
return isDarkMode.value ? accent.value : lightenOklch(accent.value, 0.5)
})

const weeklyDownloads = ref<WeeklyDownloadPoint[]>([])
Expand Down Expand Up @@ -99,18 +124,18 @@ const config = computed(() => {
backgroundColor: 'transparent',
animation: { show: false },
area: {
color: '#6A6A6A',
color: colors.value.borderHover,
useGradient: false,
opacity: 10,
},
dataLabel: {
offsetX: -10,
fontSize: 28,
bold: false,
color: isDarkMode.value ? '#8a8a8a' : '#696969',
color: colors.value.fg,
},
line: {
color: isDarkMode.value ? '#4a4a4a' : '#525252',
color: colors.value.borderHover,
pulse: {
show: true,
loop: true, // runs only once if false
Expand All @@ -125,17 +150,17 @@ const config = computed(() => {
},
plot: {
radius: 6,
stroke: isDarkMode.value ? '#FAFAFA' : '#0A0A0A',
stroke: isDarkMode.value ? 'oklch(0.985 0 0)' : 'oklch(0.145 0 0)',
},
title: {
text: lastDatapoint.value,
fontSize: 12,
color: isDarkMode.value ? '#8a8a8a' : '#696969',
color: colors.value.fgSubtle,
bold: false,
},
verticalIndicator: {
strokeDasharray: 0,
color: isDarkMode.value ? '#FAFAFA' : '#525252',
color: isDarkMode.value ? 'oklch(0.985 0 0)' : colors.value.fgSubtle,
},
},
}
Expand Down
Loading
Loading