All files / Files useFileHandlers.js

2.7% Statements 1/37
0% Branches 0/25
0% Functions 0/8
2.7% Lines 1/37

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 117 118 119 120 121 122 123 124 125 126 127 128 129                27x                                                                                                                                                                                                                                                
import { useCallback } from 'react';
import { useMutation, useQuery } from 'react-query';
 
import { useOkapiKy } from '@folio/stripes/core';
import { downloadBlob } from '../utils';
 
// This is copied and adapted from stripes-erm-components
 
const useFileHandlers = ({
  // When fetchFile is true, will AUTOMATICALLY fetch not only
  // fileMetadata but ALSO file itself
  fetchFile = false,
  /* fileEndpoint can be a string or
   * {
   *   download: string | function,
   *   metadata: string | function
   *   upload: string | function,
   * }
   */
  fileEndpoint,
  fileField = 'upload',
  fileMappings = {}, // A mapping from file -> formData field,
  fileId // An OPTIONAL id for a file which results in fetching and returning file metadata
}) => {
  const ky = useOkapiKy();
 
  // A method which resolves the endpoint to hit based on fileEndpoint
  const resolveEndpoint = useCallback((type, file) => {
    if (typeof fileEndpoint === 'string') {
      // We have a base endpoint, configure default behaviour
      switch (type) {
        case 'upload':
          return fileEndpoint;
        case 'download':
          return `${fileEndpoint}/${file?.id}/raw`;
        case 'metadata':
          return `${fileEndpoint}/${file.id}`;
        default:
          throw new TypeError(`Unsupported endpoint type ${type}`);
      }
    }
 
    if (typeof fileEndpoint !== 'object') {
      throw new TypeError(`Unsupported fileEndpoint type ${typeof fileEndpoint}`);
    }
    const config = fileEndpoint[type];
 
    if (config === null) {
      throw new TypeError(`Unsupported fileEndpoint[${type}] is not configured`);
    }
 
    // Finally we have configured behaviour
    switch (typeof config) {
      case 'function':
        return config(file);
      case 'string':
        return config;
      default:
        // If we have hit the end then we've done something wrong
        throw new TypeError(`Unsupported configuration for fileEndpoint[${type}]: ${typeof config}`);
    }
  }, [fileEndpoint]);
 
  const { mutateAsync: handleUploadFile } = useMutation(
    [fileEndpoint, 'handleUpload'],
    (file) => {
      const formData = new FormData();
      formData.append(fileField, file);
 
      Object.entries(fileMappings).forEach(([key, value]) => {
        if (typeof value === 'string') {
          formData.append(key, value);
        } else if (typeof value === 'function') {
          formData.append(key, value(file));
        } else {
          throw new TypeError('Invalid fileMapping: ' + key);
        }
      });
 
      return ky.post(resolveEndpoint('upload', file), { body: formData }).json();
    }
  );
 
  const {
    data: fileBlob,
    isLoading: isFileLoading,
    refetch: refetchFile
  } = useQuery({
    queryKey: [fileEndpoint, 'content', fileId],
    queryFn: () => ky.get(resolveEndpoint('download', { id: fileId })).blob(),
    enabled: !!fileId && typeof fileId === 'string' && fetchFile,
    staleTime: Infinity, // Assume the file won't change underneath us.
  });
 
 
  // We are declaratively fetching a file via a function, useMutation is more convenient here
  // NOTE even if fileBlob already exists this will refetch it for download
  const { mutate: getFile, mutateAsync: getFileAsync } = useMutation(
    [fileEndpoint, 'getFile'],
    (file) => ky.get(resolveEndpoint('download', file)).blob()
  );
 
  const handleDownloadFile = useCallback((fileMetadata) => {
    return getFileAsync(fileMetadata)
      /* In this instance we want the file name back as it was handed in, whitespace and all */
      .then(downloadBlob(fileMetadata.name, { processWhitespace: false }));
  }, [getFileAsync]);
 
  const { data: fileMetadata, isLoading: isFileMetadataLoading } = useQuery({
    queryKey: [fileEndpoint, 'metadata', fileId],
    queryFn: () => ky.get(resolveEndpoint('metadata', { id: fileId })).json(),
    enabled: !!fileId && typeof fileId === 'string',
  });
 
  return {
    file: fileBlob,
    fileMetadata,
    getFile,
    getFileAsync,
    handleDownloadFile,
    handleUploadFile,
    isFileLoading,
    isFileMetadataLoading,
    refetchFile
  };
};
 
export default useFileHandlers;