project-context.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import {
  2. FC,
  3. createContext,
  4. useContext,
  5. useMemo,
  6. useState,
  7. useCallback,
  8. } from 'react'
  9. import getMeta from '@/utils/meta'
  10. import { ProjectUpdate, ProjectMetadata } from './types/project-metadata'
  11. import { ProjectSnapshot } from '@/infrastructure/project-snapshot'
  12. import { Tag } from '../../../../app/src/Features/Tags/types'
  13. type ProjectContextValue = {
  14. projectId: ProjectMetadata['_id']
  15. project: ProjectMetadata | null
  16. joinProject: (project: ProjectMetadata) => void
  17. updateProject: (projectUpdate: ProjectUpdate) => void
  18. joinedOnce: boolean
  19. projectSnapshot: ProjectSnapshot
  20. tags: Tag[]
  21. features: ProjectMetadata['features']
  22. name: ProjectMetadata['name']
  23. }
  24. export const ProjectContext = createContext<ProjectContextValue | undefined>(
  25. undefined
  26. )
  27. export function useProjectContext() {
  28. const context = useContext(ProjectContext)
  29. if (!context) {
  30. throw new Error(
  31. 'useProjectContext is only available inside ProjectProvider'
  32. )
  33. }
  34. return context
  35. }
  36. export const ProjectProvider: FC<React.PropsWithChildren> = ({ children }) => {
  37. const [joinedOnce, setJoinedOnce] = useState(false)
  38. const [project, setProject] = useState<ProjectMetadata | null>(null)
  39. // Expose some project properties with fallbacks for convenience
  40. const projectId = project ? project._id : getMeta('ol-project_id')
  41. const name = project ? project.name : ''
  42. const features = project ? project.features : {}
  43. const joinProject = useCallback((projectData: ProjectMetadata) => {
  44. setProject(projectData)
  45. setJoinedOnce(true)
  46. }, [])
  47. const updateProject = useCallback((projectUpdateData: ProjectUpdate) => {
  48. setProject(projectData => {
  49. // Only perform the update if `project` is already set, otherwise we could
  50. // end up with an incomplete project object
  51. if (!projectData) {
  52. throw new Error('Project not initialized. Use joinProject first.')
  53. }
  54. return Object.assign({}, projectData, projectUpdateData)
  55. })
  56. }, [])
  57. const [projectSnapshot] = useState(() => new ProjectSnapshot(projectId))
  58. const tags = useMemo(
  59. () =>
  60. (getMeta('ol-projectTags') || [])
  61. // `tag.name` data may be null for some old users
  62. .map((tag: any) => ({ ...tag, name: tag.name ?? '' })),
  63. []
  64. )
  65. const value = {
  66. projectId,
  67. project,
  68. joinProject,
  69. updateProject,
  70. joinedOnce,
  71. projectSnapshot,
  72. tags,
  73. features,
  74. name,
  75. }
  76. return (
  77. <ProjectContext.Provider value={value}>{children}</ProjectContext.Provider>
  78. )
  79. }