use-status-favicon.ts 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
  2. import { useEffect, useState } from 'react'
  3. import usePreviousValue from '@/shared/hooks/use-previous-value'
  4. const RESET_AFTER_MS = 5_000
  5. const COMPILE_ICONS = {
  6. ERROR: '/favicon-error.svg',
  7. COMPILING: '/favicon-compiling.svg',
  8. COMPILED: '/favicon-compiled.svg',
  9. UNCOMPILED: '/favicon.svg',
  10. }
  11. type CompileStatus = keyof typeof COMPILE_ICONS
  12. const useCompileStatus = (): CompileStatus => {
  13. const compileContext = useCompileContext()
  14. if (compileContext.uncompiled) return 'UNCOMPILED'
  15. if (compileContext.compiling) return 'COMPILING'
  16. if (compileContext.error) return 'ERROR'
  17. return 'COMPILED'
  18. }
  19. const removeFavicon = () => {
  20. const existingFavicons = document.head.querySelectorAll(
  21. "link[rel='icon']"
  22. ) as NodeListOf<HTMLLinkElement>
  23. existingFavicons.forEach(favicon => {
  24. if (favicon.href.endsWith('.svg')) favicon.parentNode?.removeChild(favicon)
  25. })
  26. }
  27. const updateFavicon = (status: CompileStatus = 'UNCOMPILED') => {
  28. removeFavicon()
  29. const linkElement = document.createElement('link')
  30. linkElement.rel = 'icon'
  31. linkElement.href = COMPILE_ICONS[status]
  32. linkElement.type = 'image/svg+xml'
  33. linkElement.setAttribute('data-compile-status', 'true')
  34. document.head.appendChild(linkElement)
  35. }
  36. const isActive = () => !document.hidden
  37. const useIsWindowActive = () => {
  38. const [isWindowActive, setIsWindowActive] = useState(isActive())
  39. useEffect(() => {
  40. const handleVisibilityChange = () => setIsWindowActive(isActive())
  41. document.addEventListener('visibilitychange', handleVisibilityChange)
  42. return () => {
  43. document.removeEventListener('visibilitychange', handleVisibilityChange)
  44. }
  45. }, [])
  46. return isWindowActive
  47. }
  48. export const useStatusFavicon = () => {
  49. const compileStatus = useCompileStatus()
  50. const previousCompileStatus = usePreviousValue(compileStatus)
  51. const isWindowActive = useIsWindowActive()
  52. useEffect(() => {
  53. if (previousCompileStatus !== compileStatus) {
  54. return updateFavicon(compileStatus)
  55. }
  56. if (
  57. isWindowActive &&
  58. (compileStatus === 'COMPILED' || compileStatus === 'ERROR')
  59. ) {
  60. const timeout = setTimeout(updateFavicon, RESET_AFTER_MS)
  61. return () => clearTimeout(timeout)
  62. }
  63. }, [compileStatus, isWindowActive, previousCompileStatus])
  64. }