[READ-ONLY] a fast, modern browser for the npm registry
at main 171 lines 4.6 kB view raw
1import type { RemovableRef } from '@vueuse/core' 2import { useLocalStorage } from '@vueuse/core' 3import { ACCENT_COLORS } from '#shared/utils/constants' 4import type { LocaleObject } from '@nuxtjs/i18n' 5import { BACKGROUND_THEMES } from '#shared/utils/constants' 6 7type BackgroundThemeId = keyof typeof BACKGROUND_THEMES 8 9type AccentColorId = keyof typeof ACCENT_COLORS.light 10 11/** Available search providers */ 12export type SearchProvider = 'npm' | 'algolia' 13 14/** 15 * Application settings stored in localStorage 16 */ 17export interface AppSettings { 18 /** Display dates as relative (e.g., "3 days ago") instead of absolute */ 19 relativeDates: boolean 20 /** Include @types/* package in install command for packages without built-in types */ 21 includeTypesInInstall: boolean 22 /** Accent color theme */ 23 accentColorId: AccentColorId | null 24 /** Preferred background shade */ 25 preferredBackgroundTheme: BackgroundThemeId | null 26 /** Hide platform-specific packages (e.g., @scope/pkg-linux-x64) from search results */ 27 hidePlatformPackages: boolean 28 /** User-selected locale */ 29 selectedLocale: LocaleObject['code'] | null 30 /** Search provider for package search */ 31 searchProvider: SearchProvider 32 /** Connector preferences */ 33 connector: { 34 /** Automatically open the web auth page in the browser */ 35 autoOpenURL: boolean 36 } 37 sidebar: { 38 collapsed: string[] 39 } 40} 41 42const DEFAULT_SETTINGS: AppSettings = { 43 relativeDates: false, 44 includeTypesInInstall: true, 45 accentColorId: null, 46 hidePlatformPackages: true, 47 selectedLocale: null, 48 preferredBackgroundTheme: null, 49 searchProvider: import.meta.test ? 'npm' : 'algolia', 50 connector: { 51 autoOpenURL: false, 52 }, 53 sidebar: { 54 collapsed: [], 55 }, 56} 57 58const STORAGE_KEY = 'npmx-settings' 59 60// Shared settings instance (singleton per app) 61let settingsRef: RemovableRef<AppSettings> | null = null 62 63/** 64 * Composable for managing application settings with localStorage persistence. 65 * Settings are shared across all components that use this composable. 66 */ 67export function useSettings() { 68 if (!settingsRef) { 69 settingsRef = useLocalStorage<AppSettings>(STORAGE_KEY, DEFAULT_SETTINGS, { 70 mergeDefaults: true, 71 }) 72 } 73 74 return { 75 settings: settingsRef, 76 } 77} 78 79/** 80 * Composable for accessing just the relative dates setting. 81 * Useful for components that only need to read this specific setting. 82 */ 83export function useRelativeDates() { 84 const { settings } = useSettings() 85 return computed(() => settings.value.relativeDates) 86} 87 88/** 89 * Composable for managing accent color. 90 */ 91export function useAccentColor() { 92 const { settings } = useSettings() 93 const colorMode = useColorMode() 94 95 const accentColors = computed(() => { 96 const isDark = colorMode.value === 'dark' 97 const colors = isDark ? ACCENT_COLORS.dark : ACCENT_COLORS.light 98 99 return Object.entries(colors).map(([id, value]) => ({ 100 id: id as AccentColorId, 101 name: id, 102 value, 103 })) 104 }) 105 106 function setAccentColor(id: AccentColorId | null) { 107 if (id) { 108 document.documentElement.style.setProperty('--accent-color', `var(--swatch-${id})`) 109 } else { 110 document.documentElement.style.removeProperty('--accent-color') 111 } 112 settings.value.accentColorId = id 113 } 114 115 return { 116 accentColors, 117 selectedAccentColor: computed(() => settings.value.accentColorId), 118 setAccentColor, 119 } 120} 121 122/** 123 * Composable for managing the search provider setting. 124 */ 125export function useSearchProvider() { 126 const { settings } = useSettings() 127 128 const searchProvider = computed({ 129 get: () => settings.value.searchProvider, 130 set: (value: SearchProvider) => { 131 settings.value.searchProvider = value 132 }, 133 }) 134 135 const isAlgolia = computed(() => searchProvider.value === 'algolia') 136 137 function toggle() { 138 searchProvider.value = searchProvider.value === 'npm' ? 'algolia' : 'npm' 139 } 140 141 return { 142 searchProvider, 143 isAlgolia, 144 toggle, 145 } 146} 147 148export function useBackgroundTheme() { 149 const backgroundThemes = Object.entries(BACKGROUND_THEMES).map(([id, value]) => ({ 150 id: id as BackgroundThemeId, 151 name: id, 152 value, 153 })) 154 155 const { settings } = useSettings() 156 157 function setBackgroundTheme(id: BackgroundThemeId | null) { 158 if (id) { 159 document.documentElement.dataset.bgTheme = id 160 } else { 161 document.documentElement.removeAttribute('data-bg-theme') 162 } 163 settings.value.preferredBackgroundTheme = id 164 } 165 166 return { 167 backgroundThemes, 168 selectedBackgroundTheme: computed(() => settings.value.preferredBackgroundTheme), 169 setBackgroundTheme, 170 } 171}