All files / media-loader/src/MediaLoader index.tsx

100% Statements 36/36
90.9% Branches 10/11
100% Functions 6/6
100% Lines 35/35

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              1x 1x   1x 1x   1x           1x                           1x 1x 1x         1x           1x           1x 5x   5x 8x 8x 14x 14x 11x 11x 11x   3x 3x 3x       3x       8x     5x 5x 5x       5x     1x 5x 5x 5x       5x  
/**
 * Copyright (c) 2024-present, Matti Bar-Zeev.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
 
import React, {Children, ReactElement, ReactNode, RefObject, cloneElement, useEffect, useRef} from 'react';
import {ImageProcessor} from './image';
import {MediaHTMLElement, MediaProcessor} from './types';
import {VideoProcessor} from './video';
import {AudioProcessor} from './audio';
 
const MEDIA_TYPE = {
    IMG_MEDIA_TYPE: 'img',
    VIDEO_MEDIA_TYPE: 'video',
    AUDIO_MEDIA_TYPE: 'audio',
} as const;
 
const MEDIA_TAG = {
    IMG: 'IMG',
    VIDEO: 'VIDEO',
    AUDIO: 'AUDIO',
} as const;
 
interface MediaLoaderProps {
    children: ReactNode;
    loadingStrategy: (
        mediaHTMLElements: RefObject<MediaHTMLElement>[],
        loadMedia: (mediaHTMLElement: MediaHTMLElement) => void
    ) => void;
}
 
const imageProcessor = new ImageProcessor();
const videoProcessor = new VideoProcessor();
const audioProcessor = new AudioProcessor();
 
type MediaTypeKey = (typeof MEDIA_TYPE)[keyof typeof MEDIA_TYPE];
type MediaTagKey = (typeof MEDIA_TAG)[keyof typeof MEDIA_TAG];
 
const mediaTypeToProcessor: Record<MediaTypeKey, MediaProcessor> = {
    [MEDIA_TYPE.IMG_MEDIA_TYPE]: imageProcessor,
    [MEDIA_TYPE.VIDEO_MEDIA_TYPE]: videoProcessor,
    [MEDIA_TYPE.AUDIO_MEDIA_TYPE]: audioProcessor,
};
 
const mediaTagToProcessor: Record<MediaTagKey, MediaProcessor> = {
    [MEDIA_TAG.IMG]: imageProcessor,
    [MEDIA_TAG.VIDEO]: videoProcessor,
    [MEDIA_TAG.AUDIO]: audioProcessor,
};
 
const MediaLoader = ({children, loadingStrategy: triggerFunction}: MediaLoaderProps) => {
    const mediaHTMLElements = useRef<RefObject<MediaHTMLElement>[]>([]);
 
    const getChildren = (currentChildren: ReactNode): ReactElement[] => {
        const childrenArray = Children.toArray(currentChildren) as ReactElement[];
        const mediaChildren = Children.map(childrenArray, (child) => {
            const mediaProcessor = mediaTypeToProcessor[child.type as MediaTypeKey];
            if (mediaProcessor) {
                const [element, ref] = mediaProcessor.getProcessedChildren(child);
                mediaHTMLElements.current.push(ref);
                return element;
            } else {
                let result = child;
                if (child?.props?.children) {
                    result = cloneElement(child, {
                        children: getChildren(child.props.children),
                    });
                }
                return result;
            }
        });
 
        return mediaChildren;
    };
 
    useEffect(() => {
        if (triggerFunction) {
            triggerFunction(mediaHTMLElements.current, loadMedia);
        }
    }, []);
 
    return <>{getChildren(children)}</>;
};
 
const loadMedia = (mediaHTMLElement: MediaHTMLElement) => {
    const mediaProcessor = mediaTagToProcessor[mediaHTMLElement.tagName as MediaTagKey];
    if (mediaProcessor) {
        mediaProcessor.loadMedia(mediaHTMLElement);
    }
};
 
export default MediaLoader;