| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 | 
							- import React, { CSSProperties, FC, HTMLAttributes, InputHTMLAttributes, useEffect, useRef, useState } from 'react';
 
- const supportsInputDirs = 'webkitdirectory' in HTMLInputElement.prototype;
 
- const supportsRelativePath = 'webkitRelativePath' in File.prototype;
 
- const supportsDirs = typeof DataTransferItem != 'undefined' && 'webkitGetAsEntry' in DataTransferItem.prototype;
 
- const readRecurse = (dir: FileSystemDirectoryEntry, onComplete: (files: File[]) => void, onError: (err: Error) => void) => {
 
-   let files: File[] = [];
 
-   let total = 0;
 
-   let errored = false;
 
-   let reachedEnd = false;
 
-   const onErr = (err: Error) => {
 
-     if (!errored) {
 
-       errored = true;
 
-       onError(err);
 
-     }
 
-   };
 
-   const onDone = (f: File[]) => {
 
-     files = files.concat(f);
 
-     if (!--total && reachedEnd) onComplete(files);
 
-   };
 
-   const reader = dir.createReader();
 
-   const onRead = (entries: FileSystemEntry[]) => {
 
-     if (!entries.length && !errored) {
 
-       if (!total) onComplete(files);
 
-       else reachedEnd = true;
 
-     } else reader.readEntries(onRead, onError);
 
-     for (const entry of entries) {
 
-       ++total;
 
-       if (entry.isFile) entry.file(f => onDone([
 
-         new File([f], entry.fullPath.slice(1), f)
 
-       ]), onErr);
 
-       else readRecurse(entry as FileSystemDirectoryEntry, onDone, onErr);
 
-     }
 
-   };
 
-   reader.readEntries(onRead, onError);
 
- }
 
- const FilePicker: FC<{
 
-   onFiles(files: File[] | null): void;
 
-   onDrag(on: boolean): void;
 
-   onError(err: string | Error): void;
 
-   allowDirs: boolean;
 
- } & HTMLAttributes<HTMLDivElement>
 
- > = ({ onFiles, onDrag, onError, style, allowDirs, children, ...props }) => {
 
-   const inputRef = useRef<HTMLInputElement>(null);
 
-   const dirInputRef = useRef<HTMLInputElement>(null);
 
-   const dragRef = useRef(0);
 
-   const [inputHover, setInputHover] = useState(false);
 
-   const [dirInputHover, setDirInputHover] = useState(false);
 
-   const [isHovering, setIsHovering] = useState(false);
 
-   useEffect(() => {
 
-     // only init'd when support dirs
 
-     if (dirInputRef.current) {
 
-       dirInputRef.current.setAttribute('webkitdirectory', '');
 
-     }
 
-   }, []);
 
-   const rootProps: HTMLAttributes<HTMLDivElement> = {
 
-     onDrop(ev) {
 
-       ev.preventDefault();
 
-       const tf = ev.dataTransfer;
 
-       if (!tf.files.length) onError('Please drop some files in');
 
-       else {
 
-         onFiles(null);
 
-         if (supportsDirs && allowDirs) {
 
-           let outFiles: File[] = [];
 
-           let lft = tf.items.length;
 
-           let errored = false;
 
-           const onErr = (err: Error) => {
 
-             if (!errored) {
 
-               errored = true;
 
-               onError(err);
 
-             }
 
-           }
 
-           const onDone = (f: File[]) => {
 
-             outFiles = outFiles.concat(f);
 
-             if (!--lft && !errored) onFiles(outFiles);
 
-           };
 
-           for (let i = 0; i < tf.items.length; ++i) {
 
-             const entry = tf.items[i].webkitGetAsEntry();
 
-             if (entry.isFile) entry.file(f => onDone([f]), onErr);
 
-             else readRecurse(entry as FileSystemDirectoryEntry, onDone, onErr);
 
-           }
 
-         } else onFiles(Array.prototype.slice.call(tf.files));
 
-       }
 
-       setIsHovering(false);
 
-     },
 
-     onDragEnter() {
 
-       ++dragRef.current;
 
-       onDrag(true);
 
-       setIsHovering(true);
 
-     },
 
-     onDragOver(ev) {
 
-       ev.preventDefault();
 
-     },
 
-     onDragLeave() {
 
-       if (!--dragRef.current) {
 
-         onDrag(false);  
 
-         setIsHovering(false);
 
-       }
 
-     },
 
-     style: {
 
-       display: 'flex',
 
-       flexDirection: 'column',
 
-       alignItems: 'center',
 
-       ...style
 
-     }
 
-   };
 
-   const inputProps: InputHTMLAttributes<HTMLInputElement> = {
 
-     onInput(ev) {
 
-       const t = ev.currentTarget, files = t.files!;
 
-       if (supportsRelativePath) {
 
-         const outFiles: File[] = Array(files.length);
 
-         for (let i = 0; i < files.length; ++i) {
 
-           const file = files[i];
 
-           outFiles[i] = new File([file], file.webkitRelativePath || file.name, file);
 
-         }
 
-         onFiles(outFiles);
 
-       } else onFiles(Array.prototype.slice.call(files));
 
-       t.value = '';
 
-     },
 
-     style: { display: 'none' },
 
-     multiple: true
 
-   };
 
-   const buttonStyles: CSSProperties = {
 
-     cursor: 'default',
 
-     minWidth: '8vw',
 
-     height: '6vh',
 
-     margin: '1vmin',
 
-     padding: '1vmin',
 
-     display: 'flex',
 
-     alignItems: 'center',
 
-     justifyContent: 'center',
 
-     boxShadow: '0 1px 2px 1px rgba(0, 0, 0, 0.2), 0 2px 4px 2px rgba(0, 0, 0, 0.15), 0 4px 8px 4px rgba(0, 0, 0, 0.12)',
 
-     border: '1px solid black',
 
-     borderRadius: '6px',
 
-     transition: 'background-color 300ms ease-in-out',
 
-     WebkitTouchCallout: 'none',
 
-     WebkitUserSelect: 'none',
 
-     msUserSelect: 'none',
 
-     MozUserSelect: 'none',
 
-     userSelect: 'none'
 
-   };
 
-   return (
 
-     <div {...props} {...rootProps}>
 
-       {children}
 
-       <div style={{
 
-         transition: 'transform ' + (isHovering ? 300 : 50) + 'ms ease-in-out',
 
-         transform: isHovering ? 'scale(1.5)' : 'none'
 
-       }}>Drag and Drop</div>
 
-       <div style={{
 
-         borderBottom: '1px solid gray',
 
-         margin: '1.5vh',
 
-         color: 'gray',
 
-         lineHeight: 0,
 
-         paddingTop: '1.5vh',
 
-         marginBottom: '3vh',
 
-         width: '100%',
 
-       }}>
 
-         <span style={{ background: 'white', padding: '0.25em' }}>OR</span>
 
-       </div>
 
-       <div style={{
 
-         display: 'flex',
 
-         flexDirection: 'row',
 
-         alignItems: 'center',
 
-         justifyContent: 'center',
 
-         width: '100%'
 
-       }}>
 
-         <input type="file" ref={inputRef} {...inputProps} />
 
-         <div onClick={() => inputRef.current!.click()} onMouseOver={() => setInputHover(true)} onMouseOut={() => setInputHover(false)} style={{
 
-           ...buttonStyles,
 
-           backgroundColor: inputHover ? 'rgba(0, 0, 0, 0.14)' : 'white'
 
-         }}>Select Files</div>
 
-         {supportsInputDirs && allowDirs &&
 
-           <>
 
-             <div style={{ boxShadow: '1px 0 black', height: '100%' }}><span /></div>
 
-             <input type="file" ref={dirInputRef} {...inputProps} />
 
-             <div onClick={() => dirInputRef.current!.click()} onMouseOver={() => setDirInputHover(true)} onMouseOut={() => setDirInputHover(false)} style={{
 
-               ...buttonStyles,
 
-               marginLeft: '8vmin',
 
-               backgroundColor: dirInputHover ? 'rgba(0, 0, 0, 0.14)' : 'white'
 
-             }}>Select Folders</div>
 
-           </>
 
-         }
 
-       </div>
 
-     </div>
 
-   );
 
- }
 
- export default FilePicker;
 
 
  |