use-detach-layout.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import { useCallback, useState, useEffect, useRef } from 'react'
  2. import { useDetachContext } from '../context/detach-context'
  3. import getMeta from '../../utils/meta'
  4. import { buildUrlWithDetachRole } from '../utils/url-helper'
  5. import * as eventTracking from '../../infrastructure/event-tracking'
  6. import usePreviousValue from './use-previous-value'
  7. const debugPdfDetach = getMeta('ol-debugPdfDetach')
  8. const LINKING_TIMEOUT = 60000
  9. const RELINK_TIMEOUT = 10000
  10. export default function useDetachLayout() {
  11. const { role, setRole, broadcastEvent, addEventHandler, deleteEventHandler } =
  12. useDetachContext()
  13. // isLinking: when the tab expects to be linked soon (e.g. on detach)
  14. const [isLinking, setIsLinking] = useState(false)
  15. // isLinked: when the tab is linked to another tab (of different role)
  16. const [isLinked, setIsLinked] = useState(false)
  17. const uiTimeoutRef = useRef()
  18. useEffect(() => {
  19. if (debugPdfDetach) {
  20. console.log('Effect', { isLinked })
  21. }
  22. setIsLinking(false)
  23. }, [isLinked, setIsLinking])
  24. useEffect(() => {
  25. if (debugPdfDetach) {
  26. console.log('Effect', { role, isLinked })
  27. }
  28. if (role === 'detached' && isLinked) {
  29. eventTracking.sendMB('project-layout-detached')
  30. }
  31. }, [role, isLinked])
  32. useEffect(() => {
  33. if (uiTimeoutRef.current) {
  34. clearTimeout(uiTimeoutRef.current)
  35. }
  36. if (role === 'detacher' && isLinked === false) {
  37. // the detacher tab either a) disconnected from its detached tab(s), b)is
  38. // loading and no detached tab(s) is connected yet or c) is detaching and
  39. // waiting for the detached tab to connect. Start a timeout to put
  40. // the tab back in non-detacher role in case no detached tab are connected
  41. uiTimeoutRef.current = setTimeout(
  42. () => {
  43. setRole(null)
  44. },
  45. isLinking ? LINKING_TIMEOUT : RELINK_TIMEOUT
  46. )
  47. }
  48. }, [role, isLinking, isLinked, setRole])
  49. useEffect(() => {
  50. if (debugPdfDetach) {
  51. console.log('Effect', { isLinking })
  52. }
  53. }, [isLinking])
  54. const previousRole = usePreviousValue(role)
  55. useEffect(() => {
  56. if (previousRole && !role) {
  57. eventTracking.sendMB('project-layout-reattached')
  58. }
  59. }, [previousRole, role])
  60. const reattach = useCallback(() => {
  61. broadcastEvent('reattach')
  62. setRole(null)
  63. setIsLinked(false)
  64. }, [setRole, setIsLinked, broadcastEvent])
  65. const detach = useCallback(() => {
  66. setRole('detacher')
  67. setIsLinking(true)
  68. window.open(buildUrlWithDetachRole('detached').toString(), '_blank')
  69. }, [setRole, setIsLinking])
  70. const handleEventForDetacherFromDetached = useCallback(
  71. message => {
  72. switch (message.event) {
  73. case 'connected':
  74. broadcastEvent('up')
  75. setIsLinked(true)
  76. break
  77. case 'up':
  78. setIsLinked(true)
  79. break
  80. case 'closed':
  81. setIsLinked(false)
  82. break
  83. }
  84. },
  85. [setIsLinked, broadcastEvent]
  86. )
  87. const handleEventForDetachedFromDetacher = useCallback(
  88. message => {
  89. switch (message.event) {
  90. case 'connected':
  91. broadcastEvent('up')
  92. setIsLinked(true)
  93. break
  94. case 'up':
  95. setIsLinked(true)
  96. break
  97. case 'closed':
  98. setIsLinked(false)
  99. break
  100. case 'reattach':
  101. setIsLinked(false) // set as unlinked, in case closing is not allowed
  102. window.close()
  103. break
  104. }
  105. },
  106. [setIsLinked, broadcastEvent]
  107. )
  108. const handleEventFromSelf = useCallback(
  109. message => {
  110. switch (message.event) {
  111. case 'closed':
  112. broadcastEvent('up')
  113. break
  114. }
  115. },
  116. [broadcastEvent]
  117. )
  118. const handleEvent = useCallback(
  119. message => {
  120. if (role === 'detacher') {
  121. if (message.role === 'detacher') {
  122. handleEventFromSelf(message)
  123. } else if (message.role === 'detached') {
  124. handleEventForDetacherFromDetached(message)
  125. }
  126. } else if (role === 'detached') {
  127. if (message.role === 'detacher') {
  128. handleEventForDetachedFromDetacher(message)
  129. } else if (message.role === 'detached') {
  130. handleEventFromSelf(message)
  131. }
  132. }
  133. },
  134. [
  135. role,
  136. handleEventForDetacherFromDetached,
  137. handleEventForDetachedFromDetacher,
  138. handleEventFromSelf,
  139. ]
  140. )
  141. useEffect(() => {
  142. addEventHandler(handleEvent)
  143. return () => deleteEventHandler(handleEvent)
  144. }, [addEventHandler, deleteEventHandler, handleEvent])
  145. return {
  146. reattach,
  147. detach,
  148. isLinked,
  149. isLinking,
  150. role,
  151. }
  152. }