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
35 changes: 29 additions & 6 deletions app/composables/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ export interface AppSettings {
}
}

export const DEFAULT_CHART_FILTER: AppSettings['chartFilter'] = {
averageWindow: 0,
smoothingTau: 0,
anomaliesFixed: true,
predictionPoints: 4,
}

const DEFAULT_SETTINGS: AppSettings = {
relativeDates: false,
includeTypesInInstall: true,
Expand All @@ -79,12 +86,7 @@ const DEFAULT_SETTINGS: AppSettings = {
sidebar: {
collapsed: [],
},
chartFilter: {
averageWindow: 0,
smoothingTau: 0,
anomaliesFixed: true,
predictionPoints: 4,
},
chartFilter: { ...DEFAULT_CHART_FILTER },
timelineChart: {
isZeroBased: false,
showZoom: false,
Expand Down Expand Up @@ -255,6 +257,27 @@ export function useBackgroundTheme() {
}
}

export function useChartFilter() {
const { settings } = useSettings()

const isDefault = computed(
() =>
settings.value.chartFilter.averageWindow === DEFAULT_CHART_FILTER.averageWindow &&
settings.value.chartFilter.smoothingTau === DEFAULT_CHART_FILTER.smoothingTau &&
settings.value.chartFilter.anomaliesFixed === DEFAULT_CHART_FILTER.anomaliesFixed &&
settings.value.chartFilter.predictionPoints === DEFAULT_CHART_FILTER.predictionPoints,
)

function reset() {
settings.value.chartFilter = { ...DEFAULT_CHART_FILTER }
}

return {
isDefault,
reset,
}
}

export function useCodeContainer() {
const { settings } = useSettings()

Expand Down
92 changes: 92 additions & 0 deletions app/pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const colorMode = useColorMode()
const { currentLocaleStatus, isSourceLocale } = useI18nStatus()
const keyboardShortcutsEnabled = useKeyboardShortcuts()
const { toggleCodeLigatures } = useCodeLigatures()
const { isDefault: isDefaultChartFilter, reset: resetChartFilter } = useChartFilter()

// Create a computed property to handle locale binding properly
const localeCodes = computed<LocaleObject['code'][]>(() =>
Expand Down Expand Up @@ -325,6 +326,97 @@ useSeoMeta({
/>
</div>
</section>

<!-- DATA CORRECTION Section -->
<section>
<h2 class="text-xs text-fg-muted uppercase tracking-wider mb-4">
{{ $t('settings.sections.data_correction') }}
</h2>
<div class="bg-bg-subtle border border-border rounded-lg p-4 sm:p-6">
<p class="text-xs text-fg-muted mb-4">
{{ $t('settings.data_correction_description') }}
</p>

<div class="space-y-2 text-sm">
<div class="flex justify-between items-center">
<div class="text-fg-muted flex items-center gap-1">
<span>{{ $t('package.trends.average_window') }}</span>
<TooltipApp
:text="$t('settings.data_correction_average_window_tooltip')"
position="top"
>
<span
class="i-lucide:info w-3 h-3 text-fg-subtle cursor-help"
aria-hidden="true"
/>
</TooltipApp>
</div>
<span class="font-mono text-fg"
>{{ settings.chartFilter.averageWindow }}
{{ $t('settings.data_correction_points') }}</span
>
</div>
<div class="flex justify-between items-center">
<div class="text-fg-muted flex items-center gap-1">
<span>{{ $t('package.trends.smoothing') }}</span>
<TooltipApp
:text="$t('settings.data_correction_smoothing_tooltip')"
position="top"
>
<span
class="i-lucide:info w-3 h-3 text-fg-subtle cursor-help"
aria-hidden="true"
/>
</TooltipApp>
</div>
<span class="font-mono text-fg">{{ settings.chartFilter.smoothingTau }}</span>
</div>
<div class="flex justify-between items-center">
<div class="text-fg-muted flex items-center gap-1">
<span>{{ $t('package.trends.prediction') }}</span>
<TooltipApp
:text="$t('settings.data_correction_prediction_tooltip')"
position="top"
>
<span
class="i-lucide:info w-3 h-3 text-fg-subtle cursor-help"
aria-hidden="true"
/>
</TooltipApp>
</div>
<span class="font-mono text-fg"
>{{ settings.chartFilter.predictionPoints }}
{{ $t('settings.data_correction_points') }}</span
>
</div>
<div class="flex justify-between items-center">
<div class="text-fg-muted flex items-center gap-1">
<span>{{ $t('package.trends.known_anomalies') }}</span>
<TooltipApp
:text="$t('settings.data_correction_anomalies_tooltip')"
position="top"
>
<span
class="i-lucide:info w-3 h-3 text-fg-subtle cursor-help"
aria-hidden="true"
/>
</TooltipApp>
</div>
<span class="font-mono text-fg">{{
settings.chartFilter.anomaliesFixed
? $t('settings.data_correction_enabled')
: $t('settings.data_correction_disabled')
}}</span>
</div>
</div>

<div class="border-t border-border mt-4 pt-3">
<ButtonBase size="sm" :disabled="isDefaultChartFilter" @click="resetChartFilter">
{{ $t('settings.data_correction_reset') }}
</ButtonBase>
</div>
</div>
</section>
</div>
</article>
</main>
Expand Down
14 changes: 12 additions & 2 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,11 @@
"display": "Display",
"search": "Search features",
"language": "Language",
"keyboard_shortcuts": "Keyboard shortcuts"
"keyboard_shortcuts": "Keyboard shortcuts",
"data_correction": "Data correction"
},
"data_correction_reset": "Reset to defaults",
"data_correction_description": "Download chart correction parameters are configured on the stats and compare pages. Reset them here if needed.",
"data_source": {
"label": "Data source",
"description": "Choose where npmx gets search data. Individual package pages always use the npm registry directly.",
Expand Down Expand Up @@ -323,7 +326,14 @@
"keyboard_shortcuts_enabled_description": "Keyboard shortcuts can be disabled if they conflict with other browser or system shortcuts",
"enable_code_ligatures": "Enable ligatures in code",
"enable_changelog_autoscroll": "Auto scroll to requested version",
"enable_changelog_autoscroll_description": "Auto scroll to or close to the requested version at package changelog"
"enable_changelog_autoscroll_description": "Auto scroll to or close to the requested version at package changelog",
"data_correction_average_window_tooltip": "Number of data points on each side used for bidirectional moving average smoothing.",
"data_correction_smoothing_tooltip": "Time constant for forward-backward exponential smoothing. Higher values produce smoother curves.",
"data_correction_prediction_tooltip": "Number of recent data points used to estimate the current incomplete period.",
"data_correction_anomalies_tooltip": "Interpolates over known download spikes caused by bots or CI issues.",
"data_correction_enabled": "Enabled",
"data_correction_disabled": "Disabled",
"data_correction_points": "points"
},
"i18n": {
"missing_keys": "{count} missing translation | {count} missing translations",
Expand Down
30 changes: 30 additions & 0 deletions i18n/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -825,10 +825,19 @@
},
"keyboard_shortcuts": {
"type": "string"
},
"data_correction": {
"type": "string"
}
},
"additionalProperties": false
},
"data_correction_reset": {
"type": "string"
},
"data_correction_description": {
"type": "string"
},
"data_source": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -975,6 +984,27 @@
},
"enable_changelog_autoscroll_description": {
"type": "string"
},
"data_correction_average_window_tooltip": {
"type": "string"
},
"data_correction_smoothing_tooltip": {
"type": "string"
},
"data_correction_prediction_tooltip": {
"type": "string"
},
"data_correction_anomalies_tooltip": {
"type": "string"
},
"data_correction_enabled": {
"type": "string"
},
"data_correction_disabled": {
"type": "string"
},
"data_correction_points": {
"type": "string"
}
},
"additionalProperties": false
Expand Down
48 changes: 48 additions & 0 deletions test/nuxt/composables/use-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,54 @@ describe('useSettings - keyboardShortcuts', () => {
})
})

describe('useSettings - chartFilter', () => {
beforeEach(() => {
vi.resetModules()
})

it('should have correct default values', async () => {
const { DEFAULT_CHART_FILTER } = await import('~/composables/useSettings')
expect(DEFAULT_CHART_FILTER).toEqual({
averageWindow: 0,
smoothingTau: 0,
anomaliesFixed: true,
predictionPoints: 4,
})
})

it('should report isDefault as true when all values match defaults', async () => {
const { useChartFilter } = await import('~/composables/useSettings')
const { isDefault } = useChartFilter()
expect(isDefault.value).toBe(true)
})

it('should report isDefault as false when any value differs', async () => {
const { useSettings, useChartFilter } = await import('~/composables/useSettings')
const { settings } = useSettings()
const { isDefault } = useChartFilter()

settings.value.chartFilter.averageWindow = 5
expect(isDefault.value).toBe(false)
})

it('should reset all values to defaults', async () => {
const { useSettings, useChartFilter, DEFAULT_CHART_FILTER } =
await import('~/composables/useSettings')
const { settings } = useSettings()
const { isDefault, reset } = useChartFilter()

settings.value.chartFilter.averageWindow = 5
settings.value.chartFilter.smoothingTau = 3
settings.value.chartFilter.anomaliesFixed = false
settings.value.chartFilter.predictionPoints = 10
expect(isDefault.value).toBe(false)

reset()
expect(isDefault.value).toBe(true)
expect(settings.value.chartFilter).toEqual(DEFAULT_CHART_FILTER)
})
})

describe('useSettings - codeLigatures', () => {
beforeEach(() => {
vi.resetModules()
Expand Down
Loading