diff --git a/services/web/frontend/js/shared/hooks/use-abort-controller.js b/services/web/frontend/js/shared/hooks/use-abort-controller.ts similarity index 100% rename from services/web/frontend/js/shared/hooks/use-abort-controller.js rename to services/web/frontend/js/shared/hooks/use-abort-controller.ts diff --git a/services/web/frontend/js/shared/hooks/use-browser-window.js b/services/web/frontend/js/shared/hooks/use-browser-window.ts similarity index 88% rename from services/web/frontend/js/shared/hooks/use-browser-window.js rename to services/web/frontend/js/shared/hooks/use-browser-window.ts index fa1c032ad9..47a8965e4f 100644 --- a/services/web/frontend/js/shared/hooks/use-browser-window.js +++ b/services/web/frontend/js/shared/hooks/use-browser-window.ts @@ -1,10 +1,10 @@ import { useEffect, useState } from 'react' let titleIsFlashing = false -let originalTitle -let flashIntervalHandle +let originalTitle = '' +let flashIntervalHandle: ReturnType -function flashTitle(message) { +function flashTitle(message: string) { if (document.hasFocus() || titleIsFlashing) { return } @@ -30,11 +30,11 @@ function stopFlashingTitle() { clearInterval(flashIntervalHandle) window.document.title = originalTitle - originalTitle = undefined + originalTitle = '' titleIsFlashing = false } -function setTitle(title) { +function setTitle(title: string) { if (titleIsFlashing) { originalTitle = title } else { diff --git a/services/web/frontend/js/shared/hooks/use-callback-handlers.js b/services/web/frontend/js/shared/hooks/use-callback-handlers.ts similarity index 53% rename from services/web/frontend/js/shared/hooks/use-callback-handlers.js rename to services/web/frontend/js/shared/hooks/use-callback-handlers.ts index 64a7fca6a5..38b3bfcc0b 100644 --- a/services/web/frontend/js/shared/hooks/use-callback-handlers.js +++ b/services/web/frontend/js/shared/hooks/use-callback-handlers.ts @@ -1,17 +1,17 @@ import { useCallback, useRef } from 'react' export default function useCallbackHandlers() { - const handlersRef = useRef(new Set()) + const handlersRef = useRef(new Set<(...arg: unknown[]) => void>()) - const addHandler = useCallback(handler => { + const addHandler = useCallback((handler: (...args: unknown[]) => void) => { handlersRef.current.add(handler) }, []) - const deleteHandler = useCallback(handler => { + const deleteHandler = useCallback((handler: (...args: unknown[]) => void) => { handlersRef.current.delete(handler) }, []) - const callHandlers = useCallback((...args) => { + const callHandlers = useCallback((...args: unknown[]) => { for (const handler of handlersRef.current) { handler(...args) } diff --git a/services/web/frontend/js/shared/hooks/use-debounce.js b/services/web/frontend/js/shared/hooks/use-debounce.ts similarity index 86% rename from services/web/frontend/js/shared/hooks/use-debounce.js rename to services/web/frontend/js/shared/hooks/use-debounce.ts index 1845f8f70e..5fee07b0c1 100644 --- a/services/web/frontend/js/shared/hooks/use-debounce.js +++ b/services/web/frontend/js/shared/hooks/use-debounce.ts @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react' * @param {number} delay * @returns {T} */ -export default function useDebounce(value, delay = 0) { +export default function useDebounce(value: T, delay = 0) { const [debouncedValue, setDebouncedValue] = useState(value) useEffect(() => { diff --git a/services/web/frontend/js/shared/hooks/use-deep-compare-effect.js b/services/web/frontend/js/shared/hooks/use-deep-compare-effect.ts similarity index 68% rename from services/web/frontend/js/shared/hooks/use-deep-compare-effect.js rename to services/web/frontend/js/shared/hooks/use-deep-compare-effect.ts index 6b0c6773db..637afe3cce 100644 --- a/services/web/frontend/js/shared/hooks/use-deep-compare-effect.js +++ b/services/web/frontend/js/shared/hooks/use-deep-compare-effect.ts @@ -1,8 +1,11 @@ import { useEffect, useRef } from 'react' import _ from 'lodash' -export default function useDeepCompareEffect(callback, dependencies) { - const ref = useRef() +export default function useDeepCompareEffect( + callback: () => void, + dependencies: T[] +) { + const ref = useRef() return useEffect(() => { if (_.isEqual(dependencies, ref.current)) { return diff --git a/services/web/frontend/js/shared/hooks/use-detach-action.js b/services/web/frontend/js/shared/hooks/use-detach-action.ts similarity index 73% rename from services/web/frontend/js/shared/hooks/use-detach-action.js rename to services/web/frontend/js/shared/hooks/use-detach-action.ts index e9376d85ab..32cedad82f 100644 --- a/services/web/frontend/js/shared/hooks/use-detach-action.js +++ b/services/web/frontend/js/shared/hooks/use-detach-action.ts @@ -1,22 +1,27 @@ import { useCallback, useEffect } from 'react' import { useDetachContext } from '../context/detach-context' import getMeta from '../../utils/meta' +import { DetachRole, DetachTargetRole, Message } from './use-detach-state' const debugPdfDetach = getMeta('ol-debugPdfDetach') -export default function useDetachAction( - actionName, - actionFunction, - senderRole, - targetRole +function useDetachAction< + A, + S extends DetachRole, + T extends DetachTargetRole +>( + actionName: string, + actionFunction: (...args: A[]) => void, + senderRole: S, + targetRole: T ) { const { role, broadcastEvent, addEventHandler, deleteEventHandler } = useDetachContext() - const eventName = `action-${actionName}` + const eventName: Message['event'] = `action-${actionName}` const triggerFn = useCallback( - (...args) => { + (...args: A[]) => { if (role === senderRole) { broadcastEvent(eventName, { args }) } else { @@ -27,7 +32,7 @@ export default function useDetachAction( ) const handleActionEvent = useCallback( - message => { + (message: Message) => { if (message.event !== eventName) { return } @@ -49,3 +54,5 @@ export default function useDetachAction( return triggerFn } + +export default useDetachAction diff --git a/services/web/frontend/js/shared/hooks/use-detach-layout.js b/services/web/frontend/js/shared/hooks/use-detach-layout.ts similarity index 97% rename from services/web/frontend/js/shared/hooks/use-detach-layout.js rename to services/web/frontend/js/shared/hooks/use-detach-layout.ts index d744cf340f..be54207e36 100644 --- a/services/web/frontend/js/shared/hooks/use-detach-layout.js +++ b/services/web/frontend/js/shared/hooks/use-detach-layout.ts @@ -24,7 +24,7 @@ export default function useDetachLayout() { // redundant const [isRedundant, setIsRedundant] = useState(false) - const uiTimeoutRef = useRef() + const uiTimeoutRef = useRef>() useEffect(() => { if (debugPdfDetach) { @@ -153,7 +153,7 @@ export default function useDetachLayout() { message => { if (role === 'detacher') { if (message.role === 'detacher') { - handleEventForDetacherFromDetacher(message) + handleEventForDetacherFromDetacher() } else if (message.role === 'detached') { handleEventForDetacherFromDetached(message) } diff --git a/services/web/frontend/js/shared/hooks/use-detach-state-watcher.js b/services/web/frontend/js/shared/hooks/use-detach-state-watcher.js deleted file mode 100644 index 9154535677..0000000000 --- a/services/web/frontend/js/shared/hooks/use-detach-state-watcher.js +++ /dev/null @@ -1,22 +0,0 @@ -import { useEffect } from 'react' -import useDetachState from './use-detach-state' - -export default function useDetachStateWatcher( - key, - stateValue, - senderRole, - targetRole -) { - const [value, setValue] = useDetachState( - key, - stateValue, - senderRole, - targetRole - ) - - useEffect(() => { - setValue(stateValue) - }, [setValue, stateValue]) - - return [value, setValue] -} diff --git a/services/web/frontend/js/shared/hooks/use-detach-state-watcher.ts b/services/web/frontend/js/shared/hooks/use-detach-state-watcher.ts new file mode 100644 index 0000000000..4b05aec6f8 --- /dev/null +++ b/services/web/frontend/js/shared/hooks/use-detach-state-watcher.ts @@ -0,0 +1,32 @@ +import { useEffect } from 'react' +import useDetachState, { + DetachRole, + DetachTargetRole, +} from './use-detach-state' + +type UseDetachParams = Parameters + +function useDetachStateWatcher< + S extends DetachRole, + T extends DetachTargetRole +>( + key: UseDetachParams[0], + stateValue: UseDetachParams[1], + senderRole: S, + targetRole: T +) { + const [value, setValue] = useDetachState( + key, + stateValue, + senderRole, + targetRole + ) + + useEffect(() => { + setValue(stateValue) + }, [setValue, stateValue]) + + return [value, setValue] +} + +export default useDetachStateWatcher diff --git a/services/web/frontend/js/shared/hooks/use-detach-state.js b/services/web/frontend/js/shared/hooks/use-detach-state.ts similarity index 68% rename from services/web/frontend/js/shared/hooks/use-detach-state.js rename to services/web/frontend/js/shared/hooks/use-detach-state.ts index 6b9cbf7f00..61c0bd66b8 100644 --- a/services/web/frontend/js/shared/hooks/use-detach-state.js +++ b/services/web/frontend/js/shared/hooks/use-detach-state.ts @@ -4,12 +4,24 @@ import getMeta from '../../utils/meta' const debugPdfDetach = getMeta('ol-debugPdfDetach') -export default function useDetachState( - key, - defaultValue, - senderRole, - targetRole -) { +export type DetachRole = 'detacher' | 'detached' +export type DetachTargetRole = T extends 'detacher' + ? 'detached' + : 'detacher' +export type Message = { + event: `${'action' | 'state'}-${string}` + data: { + args: DataArgs[] + value: unknown + } +} + +function useDetachState>( + key: string, + defaultValue: unknown, + senderRole: S, + targetRole: T +): [unknown, React.Dispatch] { const [value, setValue] = useState(defaultValue) const { @@ -20,7 +32,7 @@ export default function useDetachState( deleteEventHandler, } = useDetachContext() - const eventName = `state-${key}` + const eventName: Message['event'] = `state-${key}` // lastDetachedConnectedAt is added as a dependency in order to re-broadcast // all states when a new detached tab connects @@ -38,7 +50,7 @@ export default function useDetachState( ]) const handleStateEvent = useCallback( - message => { + (message: Message) => { if (message.event !== eventName) { return } @@ -60,3 +72,5 @@ export default function useDetachState( return [value, setValue] } + +export default useDetachState diff --git a/services/web/frontend/js/shared/hooks/use-dropdown.js b/services/web/frontend/js/shared/hooks/use-dropdown.ts similarity index 96% rename from services/web/frontend/js/shared/hooks/use-dropdown.js rename to services/web/frontend/js/shared/hooks/use-dropdown.ts index b58013e2b4..0f05e5ab53 100644 --- a/services/web/frontend/js/shared/hooks/use-dropdown.js +++ b/services/web/frontend/js/shared/hooks/use-dropdown.ts @@ -5,7 +5,7 @@ export default function useDropdown(defaultOpen = false) { const [open, setOpen] = useState(defaultOpen) // store the dropdown node for use in the "click outside" event listener - const ref = useRef(null) + const ref = useRef>(null) // react-bootstrap v0.x passes `component` instead of `node` to the ref callback const handleRef = useCallback( diff --git a/services/web/frontend/js/shared/hooks/use-expand-collapse.js b/services/web/frontend/js/shared/hooks/use-expand-collapse.ts similarity index 85% rename from services/web/frontend/js/shared/hooks/use-expand-collapse.js rename to services/web/frontend/js/shared/hooks/use-expand-collapse.ts index 2fa4aa6fbe..cbd11be783 100644 --- a/services/web/frontend/js/shared/hooks/use-expand-collapse.js +++ b/services/web/frontend/js/shared/hooks/use-expand-collapse.ts @@ -3,13 +3,16 @@ import classNames from 'classnames' function useExpandCollapse({ initiallyExpanded = false, - collapsedSize = '0', + collapsedSize = 0, dimension = 'height', - classes = {}, + classes = { container: '', containerCollapsed: '' }, } = {}) { - const ref = useRef() + const ref = useRef<{ scrollHeight: number; scrollWidth: number }>() const [isExpanded, setIsExpanded] = useState(initiallyExpanded) - const [sizing, setSizing] = useState({ + const [sizing, setSizing] = useState<{ + size: number | null + needsExpandCollapse: boolean | null + }>({ size: null, needsExpandCollapse: null, }) diff --git a/services/web/frontend/js/shared/hooks/use-previous-value.js b/services/web/frontend/js/shared/hooks/use-previous-value.ts similarity index 58% rename from services/web/frontend/js/shared/hooks/use-previous-value.js rename to services/web/frontend/js/shared/hooks/use-previous-value.ts index 251829a466..f11708ec95 100644 --- a/services/web/frontend/js/shared/hooks/use-previous-value.js +++ b/services/web/frontend/js/shared/hooks/use-previous-value.ts @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react' -export default function usePreviousValue(value) { - const ref = useRef() +export default function usePreviousValue(value: T) { + const ref = useRef() useEffect(() => { ref.current = value }) diff --git a/services/web/frontend/js/shared/hooks/use-scope-event-emitter.js b/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts similarity index 64% rename from services/web/frontend/js/shared/hooks/use-scope-event-emitter.js rename to services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts index 37e25e6343..dd66577187 100644 --- a/services/web/frontend/js/shared/hooks/use-scope-event-emitter.js +++ b/services/web/frontend/js/shared/hooks/use-scope-event-emitter.ts @@ -1,16 +1,14 @@ import { useCallback } from 'react' import { useIdeContext } from '../context/ide-context' -/** - * @param {string} eventName - * @param {boolean} [broadcast] - * @returns function - */ -export default function useScopeEventEmitter(eventName, broadcast = true) { +export default function useScopeEventEmitter( + eventName: string, + broadcast = true +) { const { $scope } = useIdeContext() return useCallback( - (...detail) => { + (...detail: unknown[]) => { if (broadcast) { $scope.$broadcast(eventName, ...detail) } else { diff --git a/services/web/frontend/js/shared/hooks/use-scope-event-listener.js b/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts similarity index 62% rename from services/web/frontend/js/shared/hooks/use-scope-event-listener.js rename to services/web/frontend/js/shared/hooks/use-scope-event-listener.ts index 2606b563b7..cbc2d200c1 100644 --- a/services/web/frontend/js/shared/hooks/use-scope-event-listener.js +++ b/services/web/frontend/js/shared/hooks/use-scope-event-listener.ts @@ -1,11 +1,10 @@ import { useEffect } from 'react' import { useIdeContext } from '../context/ide-context' -/** - * @param {string} eventName - * @param {function} [listener] - */ -export default function useScopeEventListener(eventName, listener) { +export default function useScopeEventListener( + eventName: string, + listener: (...args: unknown[]) => void +) { const { $scope } = useIdeContext() useEffect(() => { diff --git a/services/web/frontend/js/shared/hooks/use-stop-on-first-error.js b/services/web/frontend/js/shared/hooks/use-stop-on-first-error.ts similarity index 78% rename from services/web/frontend/js/shared/hooks/use-stop-on-first-error.js rename to services/web/frontend/js/shared/hooks/use-stop-on-first-error.ts index 0e5bf321b7..05dc7e36e6 100644 --- a/services/web/frontend/js/shared/hooks/use-stop-on-first-error.js +++ b/services/web/frontend/js/shared/hooks/use-stop-on-first-error.ts @@ -3,14 +3,23 @@ import { useDetachCompileContext as useCompileContext } from '../context/detach- import { useProjectContext } from '../context/project-context' import * as eventTracking from '../../infrastructure/event-tracking' -export function useStopOnFirstError(opts = {}) { +type UseStopOnFirstErrorProps = { + eventSource?: string +} + +export function useStopOnFirstError(opts: UseStopOnFirstErrorProps = {}) { const { eventSource } = opts const { stopOnFirstError, setStopOnFirstError } = useCompileContext() const { _id: projectId } = useProjectContext() + type Opts = { + projectId: string + source?: UseStopOnFirstErrorProps['eventSource'] + } + const enableStopOnFirstError = useCallback(() => { if (!stopOnFirstError) { - const opts = { projectId } + const opts: Opts = { projectId } if (eventSource) { opts.source = eventSource } @@ -20,7 +29,7 @@ export function useStopOnFirstError(opts = {}) { }, [eventSource, projectId, stopOnFirstError, setStopOnFirstError]) const disableStopOnFirstError = useCallback(() => { - const opts = { projectId } + const opts: Opts = { projectId } if (eventSource) { opts.source = eventSource }