use-set-overall-theme.tsx 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { useCallback, useEffect, useState } from 'react'
  2. import _ from 'lodash'
  3. import type { OverallThemeMeta } from '../../../../../types/project-settings'
  4. import { saveUserSettings } from '../utils/api'
  5. import { UserSettings } from '../../../../../types/user-settings'
  6. import { useUserSettingsContext } from '@/shared/context/user-settings-context'
  7. import getMeta from '@/utils/meta'
  8. import { isBootstrap5 } from '@/features/utils/bootstrap-5'
  9. import { isIEEEBranded } from '@/utils/is-ieee-branded'
  10. import { useLayoutContext } from '@/shared/context/layout-context'
  11. export default function useSetOverallTheme() {
  12. const [chosenTheme, setChosenTheme] = useState<OverallThemeMeta | null>(null)
  13. const { loadingStyleSheet, setLoadingStyleSheet } = useLayoutContext()
  14. const { userSettings, setUserSettings } = useUserSettingsContext()
  15. const { overallTheme } = userSettings
  16. const setOverallTheme = useCallback(
  17. (overallTheme: UserSettings['overallTheme']) => {
  18. setUserSettings(settings => ({ ...settings, overallTheme }))
  19. },
  20. [setUserSettings]
  21. )
  22. const skipLoadingStyleSheet = isBootstrap5()
  23. useEffect(() => {
  24. // Sets `data-theme` attribute to the body element, needed for Bootstrap 5 theming
  25. const theme =
  26. overallTheme === 'light-' && !isIEEEBranded() ? 'light' : 'default'
  27. document.body.dataset.theme = theme
  28. }, [overallTheme])
  29. useEffect(() => {
  30. if (skipLoadingStyleSheet) {
  31. return
  32. }
  33. const docHeadEl = document.querySelector('head')
  34. const oldStyleSheetEl = document.getElementById('main-stylesheet')
  35. const newStyleSheetEl = document.createElement('link')
  36. newStyleSheetEl.setAttribute('rel', 'stylesheet')
  37. newStyleSheetEl.setAttribute('id', 'main-stylesheet')
  38. newStyleSheetEl.setAttribute('href', chosenTheme?.path ?? '')
  39. const loadEventCallback = () => {
  40. setLoadingStyleSheet(false)
  41. if (docHeadEl && oldStyleSheetEl) {
  42. docHeadEl.removeChild(oldStyleSheetEl)
  43. }
  44. }
  45. if (loadingStyleSheet) {
  46. newStyleSheetEl.addEventListener('load', loadEventCallback, {
  47. once: true,
  48. })
  49. docHeadEl?.appendChild(newStyleSheetEl)
  50. }
  51. return () => {
  52. newStyleSheetEl.removeEventListener('load', loadEventCallback)
  53. }
  54. }, [
  55. loadingStyleSheet,
  56. setLoadingStyleSheet,
  57. skipLoadingStyleSheet,
  58. chosenTheme?.path,
  59. ])
  60. return useCallback(
  61. (newOverallTheme: UserSettings['overallTheme']) => {
  62. if (overallTheme !== newOverallTheme) {
  63. const chosenTheme = _.find(
  64. getMeta('ol-overallThemes'),
  65. theme => theme.val === newOverallTheme
  66. )
  67. if (chosenTheme) {
  68. if (!skipLoadingStyleSheet) {
  69. setLoadingStyleSheet(true)
  70. }
  71. setChosenTheme(chosenTheme)
  72. setOverallTheme(newOverallTheme)
  73. saveUserSettings('overallTheme', newOverallTheme)
  74. }
  75. }
  76. },
  77. [overallTheme, setLoadingStyleSheet, skipLoadingStyleSheet, setOverallTheme]
  78. )
  79. }