theme.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. import { useSessionStore } from '#shared/stores/session.ts'
  3. import { defineStore } from 'pinia'
  4. import { readonly, ref, watch } from 'vue'
  5. type AppThemeName = 'dark' | 'light'
  6. const getRoot = () => document.querySelector(':root') as HTMLElement
  7. const getPreferredTheme = (): AppThemeName => {
  8. return window.matchMedia('(prefers-color-scheme: dark)').matches
  9. ? 'dark'
  10. : 'light'
  11. }
  12. const sanitizeTheme = (theme: string): AppThemeName => {
  13. if (['dark', 'light'].includes(theme)) return theme as AppThemeName
  14. return getPreferredTheme()
  15. }
  16. const getDOMTheme = () => {
  17. const { theme } = getRoot().dataset
  18. return theme ? sanitizeTheme(theme) : getPreferredTheme()
  19. }
  20. const saveDOMTheme = (theme: AppThemeName) => {
  21. getRoot().dataset.theme = theme
  22. }
  23. export const useAppTheme = defineStore('theme', () => {
  24. const session = useSessionStore()
  25. const getStoredTheme = () =>
  26. session.user?.preferences?.theme as AppThemeName | undefined
  27. const storeTheme = async (newTheme: AppThemeName) => {
  28. // TODO save preferences via API
  29. console.log('storeTheme', newTheme)
  30. }
  31. // the value is changed lated based on the stored theme
  32. const theme = ref<AppThemeName>('light')
  33. function saveTheme(newTheme: AppThemeName, persistent: true): Promise<void>
  34. function saveTheme(newTheme: AppThemeName, persistent: false): void
  35. function saveTheme(
  36. newTheme: AppThemeName,
  37. persistent: boolean,
  38. ): Promise<void> | void
  39. // eslint-disable-next-line func-style
  40. function saveTheme(newTheme: AppThemeName, persistent: boolean) {
  41. const sanitizedTheme = sanitizeTheme(newTheme)
  42. theme.value = sanitizedTheme
  43. saveDOMTheme(sanitizedTheme)
  44. if (persistent) {
  45. return storeTheme(sanitizedTheme)
  46. }
  47. }
  48. function toggleTheme(persistent: false): void
  49. function toggleTheme(persistent: true): Promise<void>
  50. function toggleTheme(persistent: boolean): Promise<void> | void
  51. // eslint-disable-next-line func-style
  52. function toggleTheme(persistent: boolean) {
  53. const newTheme = theme.value === 'dark' ? 'light' : 'dark'
  54. return saveTheme(newTheme, persistent)
  55. }
  56. // sync theme in case HTML value was not up-to-date when we loaded user preferences
  57. const syncTheme = () => {
  58. const domTheme = getDOMTheme()
  59. if (domTheme !== theme.value) {
  60. saveDOMTheme(theme.value)
  61. }
  62. }
  63. const storedTheme = getStoredTheme()
  64. saveTheme(storedTheme || getDOMTheme(), false)
  65. window
  66. .matchMedia('(prefers-color-scheme: dark)')
  67. .addEventListener('change', () => {
  68. // don't override preferred theme if user has already selected one
  69. const theme = getStoredTheme() || getPreferredTheme()
  70. saveTheme(theme, false)
  71. })
  72. // in case user changes the theme in another tab
  73. watch(
  74. () => getStoredTheme(),
  75. (newTheme) => {
  76. if (newTheme && theme.value !== newTheme) {
  77. saveTheme(newTheme, false)
  78. }
  79. },
  80. )
  81. return {
  82. theme: readonly(theme),
  83. toggleTheme,
  84. syncTheme,
  85. }
  86. })