# All files / src/FileSelect/ProgressAnnouncer ProgressAnnouncer.tsx

91.83% Statements 45/49
63.15% Branches 12/19
90% Functions 9/10
90.9% Lines 40/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    1173x
    3x
    3x
    3x

    3x

    1x

    1x

    1x
    1x
    1x

    1x

    1x

    3x
    33x
    33x
    33x

    33x
    33x

    33x

    33x
    1x

    1x

    1x

    33x

    33x
    9x

    33x
    32x
    1x
    1x

    1x

    1x
    1x
    1x

    33x
    8x

    33x

    3x
    1x

    3x
     import { VisuallyHidden } from '@react-aria/visually-hidden'
    import throttle from 'lodash.throttle'
    import React from 'react'
    import { useI18nContext } from '../../_hooks/I18n'
    import { ProgressAnnouncerProps } from './ProgressAnnouncer.types'

    interface Info {
      total: number
      totalProgress: number
      failed: number
      uploaded: number
    }

    const getInfo = (
      items: ProgressAnnouncerProps['items'],
      uploadProgress: ProgressAnnouncerProps['uploadProgress']
    ): Info => {
      const info = items.reduce(
        (acc, item) => {
          Iif (item.error) {
            return {
              ...acc,
              failed: acc.failed + 1,
            }
          }

          const progress = uploadProgress[item.id] ?? 0
          const totalProgress = acc.totalProgress + progress
          const uploaded = progress === 100 ? acc.uploaded + 1 : acc.uploaded

          return {
            ...acc,
            totalProgress,
            uploaded,
          }
        },
        { totalProgress: 0, failed: 0, uploaded: 0 }
      )

      return {
        ...info,
        total: items.length,
        // 33.3333333333% => 33%
        totalProgress: Math.round(info.totalProgress / items.length),
      }
    }

    export const ProgressAnnouncer: React.FC<ProgressAnnouncerProps> = ({
      items,
      uploadProgress,
      throttleMs = 5000,
    }) => {
      const i18n = useI18nContext()
      const [announce, setAnnounce] = React.useState<string>('')
      // Needed to not announce two times that progress is 100%
      const isAllUploadedAnnounced = React.useRef<boolean>(false)

      const updateAnnounce = (files: ProgressAnnouncerProps['items']) => {
        const info = getInfo(files, uploadProgress)

        const totalProgressAnnounce = info.failed
          ? i18n.t('core.dropzone.uploadTotalProgress.withFailed', info)
          : i18n.t('core.dropzone.uploadTotalProgress.withoutFailed', info)

        setAnnounce(totalProgressAnnounce)
      }

      const updateAnnounceThrottled = React.useCallback(
        throttle(
          updateAnnounce,
          throttleMs,
          // After file selection VoiceOver announces - the current location is the browser window.
          // It takes some time.
          { leading: false }
        ),
        [throttleMs]
      )
      React.useEffect(() => {
        isAllUploadedAnnounced.current = false
      }, [items.length])

      React.useEffect(() => {
        if (items.length > 0) {
          const isAllUploaded = items.every(
            (item) => uploadProgress[item.id] === 100
          )

          if (isAllUploaded && !isAllUploadedAnnounced.current) {
            // announce immediately
            isAllUploadedAnnounced.current = true
            updateAnnounceThrottled.cancel()
            updateAnnounce(items)
          } else IEif (!isAllUploaded) {
            updateAnnounceThrottled(items)
          }
        }
      }, [items, uploadProgress])

      React.useEffect(() => {
        return () => {
          updateAnnounceThrottled.cancel()
        }
      }, [])

      return announce ? <MemoizedAnnouncer value={announce} /> : null
    }

    const Announcer: React.FC<{ value: string }> = ({ value }) => {
      return (
        <VisuallyHidden>
          <div role="status">{value}</div>
        </VisuallyHidden>
      )
    }

    const MemoizedAnnouncer = React.memo(Announcer)