use-detach-layout.ts 5.2 KB

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