use-detach-layout.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. // isRedundant: when a second detacher tab is opened, the first becomes
  18. // redundant
  19. const [isRedundant, setIsRedundant] = useState(false)
  20. const uiTimeoutRef = useRef()
  21. useEffect(() => {
  22. if (debugPdfDetach) {
  23. console.log('Effect', { isLinked })
  24. }
  25. setIsLinking(false)
  26. }, [isLinked, setIsLinking])
  27. useEffect(() => {
  28. if (debugPdfDetach) {
  29. console.log('Effect', { role, isLinked })
  30. }
  31. if (role === 'detached' && isLinked) {
  32. eventTracking.sendMB('project-layout-detached')
  33. }
  34. }, [role, isLinked])
  35. useEffect(() => {
  36. if (uiTimeoutRef.current) {
  37. clearTimeout(uiTimeoutRef.current)
  38. }
  39. if (role === 'detacher' && isLinked === false) {
  40. // the detacher tab either a) disconnected from its detached tab(s), b)is
  41. // loading and no detached tab(s) is connected yet or c) is detaching and
  42. // waiting for the detached tab to connect. Start a timeout to put
  43. // the tab back in non-detacher role in case no detached tab are connected
  44. uiTimeoutRef.current = setTimeout(
  45. () => {
  46. setRole(null)
  47. },
  48. isLinking ? LINKING_TIMEOUT : RELINK_TIMEOUT
  49. )
  50. }
  51. }, [role, isLinking, isLinked, setRole])
  52. useEffect(() => {
  53. if (debugPdfDetach) {
  54. console.log('Effect', { isLinking })
  55. }
  56. }, [isLinking])
  57. const previousRole = usePreviousValue(role)
  58. useEffect(() => {
  59. if (previousRole && !role) {
  60. eventTracking.sendMB('project-layout-reattached')
  61. }
  62. }, [previousRole, role])
  63. const reattach = useCallback(() => {
  64. broadcastEvent('reattach')
  65. setRole(null)
  66. setIsLinked(false)
  67. }, [setRole, setIsLinked, broadcastEvent])
  68. const detach = useCallback(() => {
  69. setIsRedundant(false)
  70. setRole('detacher')
  71. setIsLinking(true)
  72. window.open(buildUrlWithDetachRole('detached').toString(), '_blank')
  73. }, [setRole, setIsLinking])
  74. const handleEventForDetacherFromDetacher = useCallback(() => {
  75. if (debugPdfDetach) {
  76. console.log(
  77. 'Duplicate detacher detected, turning into a regular editor again'
  78. )
  79. }
  80. setIsRedundant(true)
  81. setIsLinked(false)
  82. setRole(null)
  83. }, [setRole, setIsLinked])
  84. const handleEventForDetacherFromDetached = useCallback(
  85. message => {
  86. switch (message.event) {
  87. case 'connected':
  88. broadcastEvent('up')
  89. setIsLinked(true)
  90. break
  91. case 'up':
  92. setIsLinked(true)
  93. break
  94. case 'closed':
  95. setIsLinked(false)
  96. break
  97. }
  98. },
  99. [setIsLinked, broadcastEvent]
  100. )
  101. const handleEventForDetachedFromDetacher = useCallback(
  102. message => {
  103. switch (message.event) {
  104. case 'connected':
  105. broadcastEvent('up')
  106. setIsLinked(true)
  107. break
  108. case 'up':
  109. setIsLinked(true)
  110. break
  111. case 'closed':
  112. setIsLinked(false)
  113. break
  114. case 'reattach':
  115. setIsLinked(false) // set as unlinked, in case closing is not allowed
  116. window.close()
  117. break
  118. }
  119. },
  120. [setIsLinked, broadcastEvent]
  121. )
  122. const handleEventForDetachedFromDetached = useCallback(
  123. message => {
  124. switch (message.event) {
  125. case 'closed':
  126. broadcastEvent('up')
  127. break
  128. }
  129. },
  130. [broadcastEvent]
  131. )
  132. const handleEvent = useCallback(
  133. message => {
  134. if (role === 'detacher') {
  135. if (message.role === 'detacher') {
  136. handleEventForDetacherFromDetacher(message)
  137. } else if (message.role === 'detached') {
  138. handleEventForDetacherFromDetached(message)
  139. }
  140. } else if (role === 'detached') {
  141. if (message.role === 'detacher') {
  142. handleEventForDetachedFromDetacher(message)
  143. } else if (message.role === 'detached') {
  144. handleEventForDetachedFromDetached(message)
  145. }
  146. }
  147. },
  148. [
  149. role,
  150. handleEventForDetacherFromDetacher,
  151. handleEventForDetacherFromDetached,
  152. handleEventForDetachedFromDetacher,
  153. handleEventForDetachedFromDetached,
  154. ]
  155. )
  156. useEffect(() => {
  157. addEventHandler(handleEvent)
  158. return () => deleteEventHandler(handleEvent)
  159. }, [addEventHandler, deleteEventHandler, handleEvent])
  160. return {
  161. reattach,
  162. detach,
  163. isLinked,
  164. isLinking,
  165. role,
  166. isRedundant,
  167. }
  168. }