浏览代码

Finished demo

Arjun Barrett 4 年之前
父节点
当前提交
4f5885d197

+ 2 - 1
docs/README.md

@@ -459,7 +459,8 @@ ___
 
 
 ▸ **unzipSync**(`data`: Uint8Array): [Unzipped](interfaces/unzipped.md)
 ▸ **unzipSync**(`data`: Uint8Array): [Unzipped](interfaces/unzipped.md)
 
 
-Synchronously decompresses a ZIP archive
+Synchronously decompresses a ZIP archive. Prefer using `unzip` for better
+performance with more than one file.
 
 
 #### Parameters:
 #### Parameters:
 
 

+ 0 - 1
package.json

@@ -62,7 +62,6 @@
     "parcel": "^2.0.0-nightly.440",
     "parcel": "^2.0.0-nightly.440",
     "parcel-config-precache-manifest": "^0.0.3",
     "parcel-config-precache-manifest": "^0.0.3",
     "preact": "^10.5.5",
     "preact": "^10.5.5",
-    "prism-react-renderer": "^1.1.1",
     "react": "^17.0.1",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
     "react-dom": "^17.0.1",
     "rmwc": "^6.1.4",
     "rmwc": "^6.1.4",

+ 27 - 9
src/demo/App.tsx

@@ -1,9 +1,17 @@
-import React, { FC, useState } from 'react';
+import React, { FC, useEffect, useRef, useState } from 'react';
 import FilePicker from './components/file-picker';
 import FilePicker from './components/file-picker';
 import CodeBox from './components/code-box';
 import CodeBox from './components/code-box';
 
 
 const App: FC = () => {
 const App: FC = () => {
-  const [files, setFiles] = useState<File[]>([])
+  const [files, setFiles] = useState<File[] | null>([]);
+  const cbRef = useRef<HTMLDivElement>(null);
+  useEffect(() => {
+    if (files && files.length) {
+      cbRef.current!.scrollIntoView({
+        behavior: 'smooth' // Hopefully IE just ignores this value
+      });
+    }
+  }, [files]);
   return (
   return (
     <>
     <>
       <div style={{
       <div style={{
@@ -40,7 +48,7 @@ const App: FC = () => {
         width: '100%',
         width: '100%',
         flex: 1
         flex: 1
       }}>
       }}>
-        <div style={{ maxWidth: '80%', fontSize: 'calc(18px + 0.6vw)', paddingTop: '4vh', paddingBottom: '2vh' }}>
+        <div style={{ maxWidth: '80%', fontSize: 'calc(15px + 0.6vw)', paddingTop: '4vh', paddingBottom: '2vh' }}>
           You've found <a href="//npmjs.com/package/fflate">fflate</a>, the fastest pure JavaScript compression library in existence.
           You've found <a href="//npmjs.com/package/fflate">fflate</a>, the fastest pure JavaScript compression library in existence.
           <br /><br />
           <br /><br />
           You can both pack and expand Zlib, GZIP, DEFLATE, or ZIP files very quickly with just a few lines of code.
           You can both pack and expand Zlib, GZIP, DEFLATE, or ZIP files very quickly with just a few lines of code.
@@ -49,13 +57,23 @@ const App: FC = () => {
           <br /><br />
           <br /><br />
           Despite utilizing multiple cores, supporting data streams, and being very memory efficient, fflate is compatible with both Node.js and browsers as old as IE11.
           Despite utilizing multiple cores, supporting data streams, and being very memory efficient, fflate is compatible with both Node.js and browsers as old as IE11.
           <br /><br />
           <br /><br />
-          You can read more about fflate on <a href="//github.com/package/fflate">GitHub</a>. Try the demo below to see its performance for yourself.
-          <br />
-          <span style={{ fontSize: '0.5em' }}>I added a <i>lot</i> of sugar (around 4 hundred lines) to the UZIP and Pako APIs to make the demo clean and asynchronous, but the fflate API is unmodified.</span>
+          You can read more about fflate on <a href="//github.com/package/fflate">GitHub</a>. Try the demo below to see its performance for yourself. The code boxes are editable; try changing parameters or using a different compression format.
+          <br /><br />
+          <span style={{ color: 'gray' }}>Disclaimer: I added a <span style={{ fontStyle: 'italic' }}>lot</span> of sugar (around 4 hundred lines) to the UZIP and Pako APIs to make the demo clean and asynchronous, but the fflate API is unmodified.</span>
+          <br /><br />
         </div>
         </div>
-        <div>
-          <FilePicker allowDirs onFiles={setFiles} onError={console.log} onDrag={() => {}} />
-          <CodeBox files={files} />
+        <div style={{
+          width: '100%',
+          display: 'flex',
+          alignItems: 'center',
+          flexDirection: 'column',
+          marginBottom: '2vh'
+        }}>
+          <FilePicker allowDirs onFiles={setFiles} onError={console.log} onDrag={() => {}}>
+            <div>{files ? ((files.length || 'No') + ' file' + (files.length == 1 ? '' : 's') + ' selected') : 'Loading...'}</div>
+            <br />
+          </FilePicker>
+          {((!files || files.length) && <CodeBox files={files!} forwardRef={cbRef} />) || null}
         </div>
         </div>
        </div>
        </div>
     </>
     </>

+ 327 - 108
src/demo/components/code-box/index.tsx

@@ -1,11 +1,15 @@
-import React, { FC, useRef, useState } from 'react';
-import Highlight, { DefaultProps } from 'prism-react-renderer';
-import Prism from './prism';
+import React, { FC, Ref, useEffect, useMemo, useRef, useState } from 'react';
+import { Prism } from './prism';
 import './prism';
 import './prism';
 import './prism.css';
 import './prism.css';
 import exec from './sandbox';
 import exec from './sandbox';
 
 
 const canStream = 'stream' in File.prototype;
 const canStream = 'stream' in File.prototype;
+const rn = 'Running...';
+const wt = 'Waiting...';
+const tm = typeof performance != 'undefined'
+  ? () => performance.now()
+  : () => Date.now();
 
 
 type Preset = {
 type Preset = {
   fflate: string;
   fflate: string;
@@ -15,48 +19,187 @@ type Preset = {
 
 
 const presets: Record<string, Preset> = {
 const presets: Record<string, Preset> = {
   'Basic GZIP compression': {
   'Basic GZIP compression': {
-    fflate: `var file = files[0];
-fileToU8(file, function(buf) {
-  fflate.gzip(buf, {
-    level: 6,
-    // These are optional, but fflate supports the metadata
-    mtime: file.lastModified,
-    filename: file.name
-  }, function(err, data) {
-    // Hope you're not causing any errors in the demo ;)
-    callback(data);
+    fflate: `var left = files.length;
+var filesLengths = {};
+// This function binds the variable "file" to the local scope, which makes
+// parallel processing possible.
+// If you use ES6, you can declare variables with "let" to automatically bind
+// the variable to the scope rather than using a separate function.
+var processFile = function(i) {
+  var file = files[i];
+  fileToU8(file, function(buf) {
+    fflate.gzip(buf, {
+      level: 6,
+
+      // These are optional, but fflate supports the metadata
+
+      mtime: file.lastModified,
+      filename: file.name
+    }, function(err, data) {
+      if (err) callback(err);
+      else {
+        filesLengths[file.name] = [data.length, file.size];
+    
+        // If you want to download the file to check it for yourself:
+        // download(data, 'myFile.gz');
+  
+        // If everyone else has finished processing already...
+        if (!--left) {
+          // Then return.
+          callback(prettySizes(filesLengths));
+        }
+      }
+    });
   });
   });
-});`,
-    uzip: `var file = files[0];
-fileToU8(file, function(buf) {
-  // UZIP doesn't natively support GZIP, but I patched in support for it.
-  // In other words, you're better off using fflate for GZIP.
+}
+for (var i = 0; i < files.length; ++i) {
+  processFile(i);
+}`,
+    uzip: `var left = files.length;
+var filesLengths = {};
+var processFile = function(i) {
+  var file = files[i];
+  fileToU8(file, function(buf) {
+
+    // UZIP doesn't natively support GZIP, but I patched in support for it.
+    // In other words, you're better off using fflate for GZIP.
+  
+    // Also, UZIP runs synchronously on the main thread. It relies on global
+    // state, so you can't even run it in the background without causing bugs.
+  
+    // But just for the sake of a performance comparison, try it out.
+    uzipWorker.gzip(buf, function(err, data) {
+      if (err) callback(err);
+      else {
+        filesLengths[file.name] = [data.length, file.size];
+        if (!--left) callback(prettySizes(filesLengths));
+      }
+    });
+  });
+}
+for (var i = 0; i < files.length; ++i) {
+  processFile(i);
+}`,
+    pako: `var left = files.length;
+var filesLengths = {};
+var processFile = function(i) {
+  var file = files[i];
+  fileToU8(file, function(buf) {
 
 
-  // Also, UZIP runs synchronously on the main thread. It relies on global
-  // state, so you can't even run it in the background without causing bugs.
+    // Unlike UZIP, Pako natively supports GZIP, and it doesn't rely on global
+    // state. However, it's still 46kB for this basic functionality as opposed
+    // to fflate's 7kB, not to mention the fact that there's no easy way to use
+    // it asynchronously. I had to add a worker proxy for this to work.
 
 
-  // But just for the sake of a performance comparison, try it out.
-  uzipWorker.gzip(buf, function(err, data) {
-    callback(data);
+    pakoWorker.gzip(buf, function(err, data) {
+      if (err) callback(err)
+      else {
+        filesLengths[file.name] = [data.length, file.size];
+        if (!--left) callback(prettySizes(filesLengths));
+      }
+    });
   });
   });
-});`,
-    pako: `var file = files[0];
-fileToU8(file, function(buf) {
-  // Unlike UZIP, Pako natively supports GZIP, and it doesn't rely on global
-  // state. However, it's still 46kB for this basic functionality as opposed
-  // to fflate's 7kB, not to mention the fact that there's no easy way to use
-  // it asynchronously. I had to add a worker proxy for this to work.
+}
+for (var i = 0; i < files.length; ++i) {
+  processFile(i);
+}`
+  },
+  'ZIP archive creation': {
+    fflate: `// fflate's ZIP API is asynchronous and parallelized (multithreaded)
+var left = files.length;
+var zipObj = {};
+var ALREADY_COMPRESSED = [
+  'zip', 'gz', 'png', 'jpg', 'jpeg', 'pdf', 'doc', 'docx', 'ppt', 'pptx',
+  'xls', 'xlsx', 'heic', 'heif', '7z', 'bz2', 'rar', 'gif', 'webp', 'webm'
+];
+
+// Yet again, this is necessary for parallelization.
+var processFile = function(i) {
+  var file = files[i];
+  var ext = file.name.slice(file.name.lastIndexOf('.') + 1);
+  fileToU8(file, function(buf) {
+    // With fflate, we can choose which files we want to compress
+    zipObj[file.name] = [buf, {
+      level: ALREADY_COMPRESSED.indexOf(ext) == -1 ? 6 : 0
+    }];
+    
+    // If we didn't want to specify options:
+    // zipObj[file.name] = buf;
+
+    if (!--left) {
+      fflate.zip(zipObj, {
+        // If you want to control options for every file, you can do so here
+        // They are merged with the per-file options (if they exist)
+        // mem: 9
+      }, function(err, out) {
+        if (err) callback(err);
+        else {
+          // You may want to try downloading to see that fflate actually works:
+          // download(out, 'fflate-demo.zip');
+          callback('Length ' + out.length);
+        }
+      });
+    }
+  });
+}
+for (var i = 0; i < files.length; ++i) {
+  processFile(i);
+}`,
+    uzip: `var left = files.length;
+var processFile = function(i) {
+  var file = files[i];
+  fileToU8(file, function(buf) {
+    // With UZIP, you cannot control the compression level of a file
+    // However, it skips compressing ZIP, JPEG, and PNG files out of the box.
+    zipObj.add(file.name, buf);
+    if (!--left) {
+      zipObj.ondata = function(err, out) {
+        if (err) callback(err);
+        else callback('Length ' + out.length);
+      }
+      zipObj.end();
+    }
+  });
+}
+// Reminder that this is custom sugar
+var zipObj = uzipWorker.zip();
+for (var i = 0; i < files.length; ++i) {
+  processFile(i);
+}`,
+    pako: `var left = files.length;
 
 
-  pakoWorker.gzip(buf, function(err, data) {
-    callback(data);
+// Internally, this uses JSZip. Despite its clean API, it suffers from
+// abysmal performance and awful compression ratios, particularly in v3.2.0
+// and up.
+// If you choose JSZip, make sure to use v3.1.5 for adequate performance
+// (2-3x slower than fflate) instead of the latest version, which is 20-30x
+// slower than fflate.
+var zipObj = pakoWorker.zip();
+var processFile = function(i) {
+  var file = files[i];
+  fileToU8(file, function(buf) {
+    // With JSZip, you cannot control the compression level of a file
+    zipObj.add(file.name, buf);
+    if (!--left) {
+      zipObj.ondata = function(err, out) {
+        if (err) callback(err);
+        else callback('Length ' + out.length);
+      }
+      zipObj.end();
+    }
   });
   });
-}); `
+}
+for (var i = 0; i < files.length; ++i) {
+  processFile(i);
+}`
   }
   }
 }
 }
 
 
 if (canStream) {
 if (canStream) {
   presets['Streaming GZIP compression'] = {
   presets['Streaming GZIP compression'] = {
     fflate: `const { AsyncGzip } = fflate;
     fflate: `const { AsyncGzip } = fflate;
+// Theoretically, you could do this on every file, but I haven't done that here
+// for the sake of simplicity.
 const file = files[0];
 const file = files[0];
 const gzipStream = new AsyncGzip({ level: 6 });
 const gzipStream = new AsyncGzip({ level: 6 });
 // We can stream the file through GZIP to reduce memory usage
 // We can stream the file through GZIP to reduce memory usage
@@ -64,69 +207,85 @@ const fakeResponse = new Response(
   file.stream().pipeThrough(toNativeStream(gzipStream))
   file.stream().pipeThrough(toNativeStream(gzipStream))
 );
 );
 fakeResponse.arrayBuffer().then(buf => {
 fakeResponse.arrayBuffer().then(buf => {
-  callback(new Uint8Array(buf));
+  callback('Length ' + buf.byteLength);
 });`,
 });`,
-    uzip: `// UZIP doesn't support streaming to any extent`,
+    uzip: `// UZIP doesn't support streaming to any extent
+callback(new Error('unsupported'));`,
     pako: `// Hundreds of lines of code to make this run on a Worker...
     pako: `// Hundreds of lines of code to make this run on a Worker...
 const file = files[0];
 const file = files[0];
 // In case this wasn't clear already, Pako doesn't actually support this,
 // In case this wasn't clear already, Pako doesn't actually support this,
 // you need to create a custom async stream. I suppose you could copy the
 // you need to create a custom async stream. I suppose you could copy the
-// code used in this demo.
+// code used in this demo, which is on GitHub under the src/demo directory.
 const gzipStream = pakoWorker.createGzip();
 const gzipStream = pakoWorker.createGzip();
 const fakeResponse = new Response(
 const fakeResponse = new Response(
   file.stream().pipeThrough(toNativeStream(gzipStream))
   file.stream().pipeThrough(toNativeStream(gzipStream))
 );
 );
 fakeResponse.arrayBuffer().then(buf => {
 fakeResponse.arrayBuffer().then(buf => {
-  callback(new Uint8Array(buf));
+  callback('Length ' + buf.byteLength);
 });`
 });`
   };
   };
 }
 }
 
 
+const availablePresets = Object.keys(presets);
+
 const CodeHighlight: FC<{
 const CodeHighlight: FC<{
   code: string;
   code: string;
+  preset: string;
   onInput: (newCode: string) => void;
   onInput: (newCode: string) => void;
-}> = ({ code, onInput }) => {
-  const tmpParen = useRef(-1);
+}> = ({ code, preset, onInput }) => {
+  const highlight = useMemo(() => ({
+    __html: Prism.highlight(code + '\n', Prism.languages.javascript, 'javascript')
+  }), [code]);
+  const pre = useRef<HTMLPreElement>(null);
+  const ta = useRef<HTMLTextAreaElement>(null);
+  useEffect(() => {
+    pre.current!.addEventListener('scroll', () => {
+      ta.current!.scrollLeft = pre.current!.scrollLeft;
+      ta.current!.style.left = pre.current!.scrollLeft + 'px';
+    }, { passive: true });
+    ta.current!.addEventListener('scroll', () => {
+      pre.current!.scrollLeft = ta.current!.scrollLeft;
+    }, { passive: true });
+  }, []);
+  useEffect(() => {
+    ta.current!.value = code;
+  }, [preset]);
   return (
   return (
-    <pre className="language-javascript" style={{
-      width: '100%',
-      height: '100%',
+    <pre ref={pre} style={{
       position: 'relative',
       position: 'relative',
       backgroundColor: '#2a2734',
       backgroundColor: '#2a2734',
       color: '#9a86fd',
       color: '#9a86fd',
-      fontSize: '0.7em'
+      maxWidth: 'calc(90vw - 2em)',
+      fontSize: '0.7em',
+      marginTop: '1em',
+      marginBottom: '1em',
+      padding: '1em',
+      overflow: 'auto',
+      fontFamily: 'Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace'
     }}>
     }}>
-      <div>
-      <Highlight Prism={Prism.Prism as unknown as DefaultProps['Prism']} code={code} language="javascript">
-        {({ tokens, getLineProps, getTokenProps }) => (
-            tokens.map((line, i) => (
-              <div {...getLineProps({ line, key: i })}>
-                {line.map((token, key) => (
-                  token.empty ? <span style={{ display: 'inline-block' }} /> : <span {...getTokenProps({ token, key })} />
-                ))}
-              </div>
-            ))
-        )}
-      </Highlight>
-      </div>
+      <div dangerouslySetInnerHTML={highlight} />
       <textarea
       <textarea
+        ref={ta}
         autoComplete="off"
         autoComplete="off"
         autoCorrect="off"
         autoCorrect="off"
         autoCapitalize="off"
         autoCapitalize="off"
         spellCheck="false"
         spellCheck="false"
         style={{
         style={{
-          border: 'unset',
+          border: 0,
           resize: 'none',
           resize: 'none',
           outline: 'none',
           outline: 'none',
           position: 'absolute',
           position: 'absolute',
           background: 'transparent',
           background: 'transparent',
+          whiteSpace: 'pre',
           top: 0,
           top: 0,
           left: 0,
           left: 0,
-          width: '100%',
-          height: '100%',
+          width: 'calc(100% - 1em)',
+          height: 'calc(100% - 2em)',
+          overflow: 'hidden',
           lineHeight: 'inherit',
           lineHeight: 'inherit',
           fontSize: 'inherit',
           fontSize: 'inherit',
           padding: 'inherit',
           padding: 'inherit',
+          paddingRight: 0,
           color: 'transparent',
           color: 'transparent',
           caretColor: 'white',
           caretColor: 'white',
           fontFamily: 'inherit'
           fontFamily: 'inherit'
@@ -135,7 +294,6 @@ const CodeHighlight: FC<{
           const t = e.currentTarget;
           const t = e.currentTarget;
           let val = t.value;
           let val = t.value;
           const loc = t.selectionStart;
           const loc = t.selectionStart;
-          let newTmpParen = -1;
           if (e.key == 'Enter') {
           if (e.key == 'Enter') {
             const lastNL = val.lastIndexOf('\n', loc - 1);
             const lastNL = val.lastIndexOf('\n', loc - 1);
             let indent = 0;
             let indent = 0;
@@ -161,55 +319,53 @@ const CodeHighlight: FC<{
                 val = val.slice(0, -end);
                 val = val.slice(0, -end);
               }
               }
             }
             }
-            val += tail;
-            t.value = val;
+            t.value = val += tail;
             t.selectionStart = t.selectionEnd = loc + indent + 1;
             t.selectionStart = t.selectionEnd = loc + indent + 1;
+            ta.current!.scrollLeft = 0;
           } else if (e.key == 'Tab') {
           } else if (e.key == 'Tab') {
-            val = val.slice(0, loc) + '  ' + val.slice(t.selectionEnd);
-            t.value = val;
+            t.value = val = val.slice(0, loc) + '  ' + val.slice(t.selectionEnd);
             t.selectionStart = t.selectionEnd = loc + 2;
             t.selectionStart = t.selectionEnd = loc + 2;
           } else if (t.selectionStart == t.selectionEnd) {
           } else if (t.selectionStart == t.selectionEnd) {
             if (e.key == 'Backspace') {
             if (e.key == 'Backspace') {
               if (val.charCodeAt(loc - 1) == 32 && !val.slice(val.lastIndexOf('\n', loc - 1), loc).trim().length) {
               if (val.charCodeAt(loc - 1) == 32 && !val.slice(val.lastIndexOf('\n', loc - 1), loc).trim().length) {
-                val = val.slice(0, loc - 2) + val.slice(loc);
-                t.value = val;
-                t.selectionStart = t.selectionEnd = loc - 2;
+                t.value = val.slice(0, loc - 1) + val.slice(loc);
+                t.selectionStart = t.selectionEnd = loc - 1;
               } else if (
               } else if (
                 (val.charAt(loc - 1) == '{' && val.charAt(loc) == '}') ||
                 (val.charAt(loc - 1) == '{' && val.charAt(loc) == '}') ||
                 (val.charAt(loc - 1) == '[' && val.charAt(loc) == ']') ||
                 (val.charAt(loc - 1) == '[' && val.charAt(loc) == ']') ||
                 (val.charAt(loc - 1) == '(' && val.charAt(loc) == ')')
                 (val.charAt(loc - 1) == '(' && val.charAt(loc) == ')')
               ) {
               ) {
-                val = val.slice(0, loc - 1) + val.slice(loc + 1);
-                t.value = val;
-                t.selectionStart = t.selectionEnd = loc - 1;
-              } else return;
+                t.value = val.slice(0, loc) + val.slice(loc + 1);
+                // hack, doesn't work always
+                t.selectionStart = t.selectionEnd = loc;
+              }
+              return;
             } else {
             } else {
-              let a: string;
               switch(e.key) {
               switch(e.key) {
                 case '{':
                 case '{':
                 case '[':
                 case '[':
                 case '(':
                 case '(':
-                  t.value = val = val.slice(0, loc) + (e.key == '{' ? '{}' : e.key == '[' ? '[]' : '()') + val.slice(loc);
-                  t.selectionStart = t.selectionEnd = newTmpParen = loc + 1;
+                  t.value = val = val.slice(0, loc) + (e.key == '{' ? '}' : e.key == '[' ? ']' : ')') + val.slice(loc);
+                  t.selectionStart = t.selectionEnd = loc;
                   break;
                   break;
                 case '}':
                 case '}':
                 case ']':
                 case ']':
-                case ')': 
+                case ')':
                   // BUG: if the cursor is moved, this false activates
                   // BUG: if the cursor is moved, this false activates
-                  if (tmpParen.current != loc) {
+                  if (e.key == val.charAt(loc)) {
+                    t.value = val.slice(0, loc) + val.slice(loc + 1);
+                    t.selectionStart = t.selectionEnd = loc;
+                  } else {
                     const lastNL = val.lastIndexOf('\n', loc - 1);
                     const lastNL = val.lastIndexOf('\n', loc - 1);
                     const sl = val.slice(lastNL, loc);
                     const sl = val.slice(lastNL, loc);
-                    t.value = val = val.slice(0, loc - (sl.length > 1 && !sl.trim().length ? 2 : 0)) + e.key + val.slice(loc);
+                    const o = loc - (sl.length > 1 && !sl.trim().length ? 2 : 0);
+                    t.value = val.slice(0, o) + val.slice(loc);
+                    t.selectionStart = t.selectionEnd = o;
                   }
                   }
-                  t.selectionEnd = t.selectionStart = loc + 1;
-                  break;
-                default:
-                  tmpParen.current = -1;
-                  return;
               }
               }
+              return;
             };
             };
           } else return;
           } else return;
-          tmpParen.current = newTmpParen;
           e.preventDefault();
           e.preventDefault();
           onInput(val);
           onInput(val);
         }}
         }}
@@ -221,8 +377,19 @@ const CodeHighlight: FC<{
   )
   )
 };
 };
 
 
-const CodeBox: FC<{files: File[]}> = ({ files }) => {
-  const [{ fflate, uzip, pako }, setCodes] = useState(presets['Streaming GZIP compression']);
+const CodeBox: FC<{files: File[]; forwardRef: Ref<HTMLDivElement>}> = ({ files, forwardRef }) => {
+  const [preset, setPreset] = useState('Basic GZIP compression');
+  const [{ fflate, uzip, pako }, setCodes] = useState(presets[preset]);
+  const [ffl, setFFL] = useState('');
+  const [uz, setUZ] = useState('');
+  const [pk, setPK] = useState('');
+  useEffect(() => {
+    if (!files) {
+      setFFL('');
+      setUZ('');
+      setPK('');
+    }
+  }, [files]);
   const onInput = (lib: 'fflate' | 'uzip' | 'pako', code: string) => {
   const onInput = (lib: 'fflate' | 'uzip' | 'pako', code: string) => {
     const codes: Preset = {
     const codes: Preset = {
       fflate,
       fflate,
@@ -231,50 +398,102 @@ const CodeBox: FC<{files: File[]}> = ({ files }) => {
     };
     };
     codes[lib] = code;
     codes[lib] = code;
     setCodes(codes);
     setCodes(codes);
+    setPreset('Custom');
   }
   }
+  const [hover, setHover] = useState(false);
   return (
   return (
-    <div style={{
+    <div ref={forwardRef} style={{
       display: 'flex',
       display: 'flex',
       flexDirection: 'column',
       flexDirection: 'column',
       justifyContent: 'space-between',
       justifyContent: 'space-between',
-      alignItems: 'center'
+      alignItems: 'center',
+      width: '100%',
+      flexWrap: 'wrap'
     }}>
     }}>
+      <div>
+      <label>Preset: </label>
+        <select value={preset} onChange={e => {
+          let newPreset = e.currentTarget.value;
+          if (newPreset != 'Custom') setCodes(presets[newPreset]);
+          setPreset(newPreset);
+        }} style={{
+          marginTop: '2em'
+        }}>
+          {availablePresets.map(preset => <option key={preset} value={preset}>{preset}</option>)}
+          <option value="Custom">Custom</option>
+        </select>
+      </div>
       <div style={{
       <div style={{
         display: 'flex',
         display: 'flex',
         flexDirection: 'row',
         flexDirection: 'row',
-        justifyContent: 'space-between',
+        justifyContent: 'space-around',
         whiteSpace: 'pre-wrap',
         whiteSpace: 'pre-wrap',
-        textAlign: 'left'
+        textAlign: 'left',
+        flexWrap: 'wrap'
       }}>
       }}>
-        <div>
+        <div style={{ padding: '2vmin' }}>
           fflate:
           fflate:
-          <CodeHighlight code={fflate} onInput={t => onInput('fflate', t)} />
-        </div>
-        <div>
-          UZIP (shimmed):
-          <CodeHighlight code={uzip} onInput={t => onInput('uzip', t)} />
+          <CodeHighlight code={fflate} preset={preset} onInput={t => onInput('fflate', t)} />
+          <span dangerouslySetInnerHTML={{ __html: ffl }} />
         </div>
         </div>
-        <div>
-          Pako (shimmed):
-          <CodeHighlight code={pako} onInput={t => onInput('pako', t)} />
+        <div style={{
+          display: 'flex',
+          flexDirection: 'row',
+          flexWrap: 'wrap',
+          justifyContent: 'space-around',
+        }}>
+          <div style={{ padding: '2vmin' }}>
+            UZIP (shimmed):
+            <CodeHighlight code={uzip} preset={preset} onInput={t => onInput('uzip', t)} />
+            <span dangerouslySetInnerHTML={{ __html: uz }} />
+          </div>
+          <div style={{ padding: '2vmin' }}>
+            Pako (shimmed):
+            <CodeHighlight code={pako} preset={preset} onInput={t => onInput('pako', t)} />
+            <span dangerouslySetInnerHTML={{ __html: pk }} />
+          </div>
         </div>
         </div>
       </div>
       </div>
-      <button onClick={() => {
-        let ts = Date.now();
+      <button disabled={pk == 'Waiting...' || pk == 'Running...'} style={{
+        cursor: 'default',
+        width: '20vmin',
+        height: '6vh',
+        fontSize: '1.25em',
+        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',
+        outline: 'none',
+        backgroundColor: hover ? 'rgba(0, 0, 0, 0.2)' : 'white'
+      }} onMouseOver={() => setHover(true)} onMouseLeave={() => setHover(false)} onClick={() => {
+        setHover(false);
+        const ts = tm();
+        setFFL(rn);
+        setUZ(wt);
+        setPK(wt);
         exec(fflate, files, out => {
         exec(fflate, files, out => {
-          console.log('fflate took', Date.now() - ts);
-          ts = Date.now();
+          const tf = tm();
+          setFFL('Finished in <span style="font-weight:bold">' + (tf - ts).toFixed(3) + 'ms</span>: ' + out);
           exec(uzip, files, out => {
           exec(uzip, files, out => {
-            console.log('uzip took', Date.now() - ts);
-            ts = Date.now();
+            const tu = tm();
+            setUZ('Finished in <span style="font-weight:bold">' + (tu - tf).toFixed(3) + 'ms:</span> ' + out);
             exec(pako, files, out => {
             exec(pako, files, out => {
-              console.log('pako took', Date.now() - ts);
-            })
-          })
+              setPK('Finished in <span style="font-weight:bold">' + (tm() - tu).toFixed(3) + 'ms:</span> ' + out);
+            });
+          });
         });
         });
       }}>Run</button>
       }}>Run</button>
     </div>
     </div>
-    
   ); 
   ); 
 }
 }
 
 

+ 82 - 14
src/demo/components/code-box/sandbox.ts

@@ -3,14 +3,25 @@ import toNativeStream from './stream-adapter';
 
 
 type Callback = (...args: unknown[]) => void;
 type Callback = (...args: unknown[]) => void;
 type WorkerProxy = Record<string, Callback>;
 type WorkerProxy = Record<string, Callback>;
+const concat = (chunks: Uint8Array[]) => {
+  const out = new Uint8Array(
+    chunks.reduce((a, v) => v.length + a, 0)
+  );
+  let loc = 0;
+  for (const chunk of chunks) {
+    out.set(chunk, loc);
+    loc += chunk.length;
+  }
+  return out;
+}
 const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
 const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
   const p: WorkerProxy = {};
   const p: WorkerProxy = {};
   for (const k of keys) {
   for (const k of keys) {
     const base = function(cb: (...args: unknown[]) => void) {
     const base = function(cb: (...args: unknown[]) => void) {
       const w = new Worker('../../util/workers.ts');
       const w = new Worker('../../util/workers.ts');
       w.postMessage([lib, k]);
       w.postMessage([lib, k]);
-      w.onmessage = function() {
-        const args: unknown[] = Array.prototype.slice.call(arguments);
+      w.onmessage = function(msg) {
+        const args = msg.data;
         args.unshift(null);
         args.unshift(null);
         cb.apply(null, args);
         cb.apply(null, args);
       }
       }
@@ -19,7 +30,16 @@ const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
     }
     }
     if (k != 'zip' && k != 'unzip') {
     if (k != 'zip' && k != 'unzip') {
       p[k] = function(dat, cb) {
       p[k] = function(dat, cb) {
-        const w = base(cb as Callback);
+        const chks: unknown[] = [];
+        const w = base((err, dat, final) => {
+          if (err) (cb as Callback)(err);
+          else {
+            if (final) {
+              if (!chks.length) (cb as Callback)(null, dat);
+              else (cb as Callback)(null, concat(chks as Uint8Array[]));
+            } else chks.push(dat);
+          }
+        });
         w.postMessage([dat, true], [(dat as Uint8Array).buffer]);
         w.postMessage([dat, true], [(dat as Uint8Array).buffer]);
       }
       }
       p['create' + k.slice(0, 1).toUpperCase() + k.slice(1)] = function() {
       p['create' + k.slice(0, 1).toUpperCase() + k.slice(1)] = function() {
@@ -28,7 +48,7 @@ const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
           trueCb(err, dat, final);
           trueCb(err, dat, final);
         });
         });
         const out = {
         const out = {
-          ondata: undefined,
+          ondata: trueCb,
           push(v: Uint8Array, f: boolean) {
           push(v: Uint8Array, f: boolean) {
             if (!out.ondata) throw 'no callback';
             if (!out.ondata) throw 'no callback';
             trueCb = out.ondata;
             trueCb = out.ondata;
@@ -42,12 +62,23 @@ const createWorkerProxy = (lib: string, keys: string[]): WorkerProxy => {
       }
       }
     } else {
     } else {
       p[k] = function() {
       p[k] = function() {
-        const w = base(arguments[arguments.length - 1]);
-        const bufs: ArrayBuffer[] = [];
-        for (const k in arguments[0]) {
-          bufs.push((arguments[k] = new Uint8Array(arguments[k])).buffer);
+        let trueCb = arguments[0];
+        const w = base((err, dat) => {
+          trueCb(err, dat);
+        });
+        const out = {
+          ondata: trueCb,
+          add(name: string, buf: Uint8Array) { 
+            buf = new Uint8Array(buf);
+            w.postMessage([name, buf], [buf.buffer]);
+          },
+          end() {
+            if (!out.ondata) throw 'no callback';
+            trueCb = out.ondata;
+            w.postMessage(null);
+          }
         }
         }
-        w.postMessage(Array.prototype.slice.call(arguments, 0, -1), bufs);
+        return out;
       }
       }
     }
     }
   }
   }
@@ -58,15 +89,46 @@ const keys = ['zip', 'unzip', 'deflate', 'inflate', 'gzip', 'gunzip', 'zlib', 'u
 
 
 const uzipWorker = createWorkerProxy('uzip', keys);
 const uzipWorker = createWorkerProxy('uzip', keys);
 const pakoWorker = createWorkerProxy('pako', keys);
 const pakoWorker = createWorkerProxy('pako', keys);
-const fr = new FileReader();
 const fileToU8 = (file: File, cb: (out: Uint8Array) => void) => {
 const fileToU8 = (file: File, cb: (out: Uint8Array) => void) => {
-  fr.onload = () => {
+  const fr = new FileReader();
+  fr.onloadend = () => {
     cb(new Uint8Array(fr.result as ArrayBuffer));
     cb(new Uint8Array(fr.result as ArrayBuffer));
   }
   }
   fr.readAsArrayBuffer(file);
   fr.readAsArrayBuffer(file);
 };
 };
 
 
-const exec = (code: string, files: File[], callback: Callback): unknown => {
+const download = (file: BlobPart, name?: string) => {
+  const url = URL.createObjectURL(new Blob([file]));
+  const dl = document.createElement('a');
+  dl.download = name || ('fflate-demo-' + Date.now() + '.dat');
+  dl.href = url;
+  dl.click();
+  URL.revokeObjectURL(url);
+}
+
+const bts = ['B', ' kB', ' MB', ' GB'];
+
+const hrbt = (bt: number) => {
+  let i = 0;
+  for (; bt > 1023; ++i) bt /= 1024;
+  return bt.toFixed((i != 0) as unknown as number) + bts[i];
+}
+
+const prettySizes = (files: Record<string, [number, number]>) => {
+  let out = '\n\n';
+  let tot = 0;
+  let totc = 0;
+  let cnt = 0;
+  for (const k in files) {
+    ++cnt;
+    out += '<span style="font-weight:bold">' + k + '</span> compressed from <span style="font-weight:bold;color:red">' + hrbt(files[k][1]) + '</span> to <span style="font-weight:bold;color:green">' + hrbt(files[k][0]) + '</span>\n';
+    totc += files[k][0];
+    tot += files[k][1];
+  }
+  return out + (cnt > 1 ? '\n\n<span style="font-weight:bold">In total, all files originally <span style="font-style:italic;color:red">' + hrbt(tot) + '</span>, compressed to <span style="font-style:italic;color:green">' + hrbt(totc) + '</span></span>' : '');
+}
+
+const exec = (code: string, files: File[], callback: Callback) => {
   const scope = {
   const scope = {
     fflate,
     fflate,
     uzipWorker,
     uzipWorker,
@@ -74,9 +136,15 @@ const exec = (code: string, files: File[], callback: Callback): unknown => {
     toNativeStream,
     toNativeStream,
     callback,
     callback,
     fileToU8,
     fileToU8,
-    files
+    files,
+    download,
+    prettySizes
   };
   };
-  return new Function('"use strict";' + Object.keys(scope).map(k => 'var ' + k + ' = this["' + k + '"];').join('') + code).call(scope);
+  try {
+    new Function('"use strict";' + Object.keys(scope).map(k => 'var ' + k + ' = this["' + k + '"];').join('') + code).call(scope);
+  } catch(e) {
+    callback(e);
+  }
 }
 }
 
 
 export default exec;
 export default exec;

+ 73 - 26
src/demo/components/file-picker/index.tsx

@@ -1,4 +1,4 @@
-import React, { CSSProperties, FC, HTMLAttributes, InputHTMLAttributes, useEffect, useRef } from 'react';
+import React, { CSSProperties, FC, HTMLAttributes, InputHTMLAttributes, useEffect, useRef, useState } from 'react';
 
 
 const supportsInputDirs = 'webkitdirectory' in HTMLInputElement.prototype;
 const supportsInputDirs = 'webkitdirectory' in HTMLInputElement.prototype;
 const supportsRelativePath = 'webkitRelativePath' in File.prototype;
 const supportsRelativePath = 'webkitRelativePath' in File.prototype;
@@ -37,7 +37,7 @@ const readRecurse = (dir: FileSystemDirectoryEntry, onComplete: (files: File[])
 }
 }
 
 
 const FilePicker: FC<{
 const FilePicker: FC<{
-  onFiles(files: File[]): void;
+  onFiles(files: File[] | null): void;
   onDrag(on: boolean): void;
   onDrag(on: boolean): void;
   onError(err: string | Error): void;
   onError(err: string | Error): void;
   allowDirs: boolean;
   allowDirs: boolean;
@@ -45,6 +45,10 @@ const FilePicker: FC<{
 > = ({ onFiles, onDrag, onError, style, allowDirs, children, ...props }) => {
 > = ({ onFiles, onDrag, onError, style, allowDirs, children, ...props }) => {
   const inputRef = useRef<HTMLInputElement>(null);
   const inputRef = useRef<HTMLInputElement>(null);
   const dirInputRef = 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(() => {
   useEffect(() => {
     // only init'd when support dirs
     // only init'd when support dirs
     if (dirInputRef.current) {
     if (dirInputRef.current) {
@@ -57,6 +61,7 @@ const FilePicker: FC<{
       const tf = ev.dataTransfer;
       const tf = ev.dataTransfer;
       if (!tf.files.length) onError('Please drop some files in');
       if (!tf.files.length) onError('Please drop some files in');
       else {
       else {
+        onFiles(null);
         if (supportsDirs && allowDirs) {
         if (supportsDirs && allowDirs) {
           let outFiles: File[] = [];
           let outFiles: File[] = [];
           let lft = tf.items.length;
           let lft = tf.items.length;
@@ -78,21 +83,26 @@ const FilePicker: FC<{
           }
           }
         } else onFiles(Array.prototype.slice.call(tf.files));
         } else onFiles(Array.prototype.slice.call(tf.files));
       }
       }
+      setIsHovering(false);
     },
     },
     onDragEnter() {
     onDragEnter() {
+      ++dragRef.current;
       onDrag(true);
       onDrag(true);
+      setIsHovering(true);
     },
     },
     onDragOver(ev) {
     onDragOver(ev) {
       ev.preventDefault();
       ev.preventDefault();
     },
     },
-    onDragExit() {
-      onDrag(false);
+    onDragLeave() {
+      if (!--dragRef.current) {
+        onDrag(false);  
+        setIsHovering(false);
+      }
     },
     },
     style: {
     style: {
       display: 'flex',
       display: 'flex',
-      flexDirection: 'row',
+      flexDirection: 'column',
       alignItems: 'center',
       alignItems: 'center',
-      justifyContent: 'spac',
       ...style
       ...style
     }
     }
   };
   };
@@ -103,7 +113,7 @@ const FilePicker: FC<{
         const outFiles: File[] = Array(files.length);
         const outFiles: File[] = Array(files.length);
         for (let i = 0; i < files.length; ++i) {
         for (let i = 0; i < files.length; ++i) {
           const file = files[i];
           const file = files[i];
-          outFiles[i] = new File([file], file.webkitRelativePath, file);
+          outFiles[i] = new File([file], file.webkitRelativePath || file.name, file);
         }
         }
         onFiles(outFiles);
         onFiles(outFiles);
       } else onFiles(Array.prototype.slice.call(files));
       } else onFiles(Array.prototype.slice.call(files));
@@ -113,29 +123,66 @@ const FilePicker: FC<{
     multiple: true
     multiple: true
   };
   };
   const buttonStyles: CSSProperties = {
   const buttonStyles: CSSProperties = {
-    cursor: 'grab'
+    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 (
   return (
     <div {...props} {...rootProps}>
     <div {...props} {...rootProps}>
       {children}
       {children}
-      <input type="file" ref={inputRef} {...inputProps} />
-      <div onClick={() => inputRef.current!.click()} style={buttonStyles}>Files</div>
-      {supportsInputDirs && allowDirs &&
-        <>
-          <div style={{
-            borderLeft: '1px solid gray',
-            color: 'gray',
-            marginLeft: '2vw',
-            height: '100%',
-            display: 'flex',
-            alignItems: 'center'
-          }}>
-            <span style={{ position: 'relative', left: '-1vw', background: 'white' }}>OR</span>
-          </div>
-          <input type="file" ref={dirInputRef} {...inputProps} />
-          <div onClick={() => dirInputRef.current!.click()} style={buttonStyles}>Folders</div>
-        </>
-      }
+      <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>
     </div>
   );
   );
 }
 }

+ 3 - 2
src/demo/index.css

@@ -2,9 +2,10 @@ html, body {
   margin: 0;
   margin: 0;
   padding: 0;
   padding: 0;
   font-family: Arial, Helvetica, sans-serif;
   font-family: Arial, Helvetica, sans-serif;
+  overflow-x: hidden;
 }
 }
 
 
 #app {
 #app {
-  height: 100vh;
-  width: 100vw;
+  min-height: 100vh;
+  overflow: hidden;
 }
 }

+ 10 - 6
src/demo/util/workers.ts

@@ -59,7 +59,7 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
           type: 'uint8array',
           type: 'uint8array',
           compressionOptions: { level: 6 }
           compressionOptions: { level: 6 }
         }).then(buf => {
         }).then(buf => {
-          wk.postMessage(buf, [buf.buffer]);
+          wk.postMessage([buf, true], [buf.buffer]);
         })
         })
       };
       };
     } else if (type == 'unzip') {
     } else if (type == 'unzip') {
@@ -75,7 +75,7 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
             }));
             }));
           }
           }
           Promise.all(bufs).then(res => {
           Promise.all(bufs).then(res => {
-            wk.postMessage(out, res);
+            wk.postMessage([out, true], res);
           });
           });
         })
         })
       }
       }
@@ -89,11 +89,14 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
       ) : new pako.Inflate({
       ) : new pako.Inflate({
         raw: type == 'deflate'
         raw: type == 'deflate'
       });
       });
+      let chk: Uint8Array;
       strm.onData = (chunk: Uint8Array) => {
       strm.onData = (chunk: Uint8Array) => {
-        wk.postMessage(chunk, [chunk.buffer]);
+        if (chk) wk.postMessage([chk, false], [chk.buffer]);
+        chk = chunk;
       };
       };
       onmessage = (ev: MessageEvent<[Uint8Array, boolean]>) => {
       onmessage = (ev: MessageEvent<[Uint8Array, boolean]>) => {
         strm.push(ev.data[0], ev.data[1]);
         strm.push(ev.data[0], ev.data[1]);
+        if (ev.data[1]) wk.postMessage([chk, true], [chk.buffer]);
       };
       };
     }
     }
   } else if (lib == 'uzip') {
   } else if (lib == 'uzip') {
@@ -103,8 +106,9 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
         if (ev.data) {
         if (ev.data) {
           zip[ev.data[0]] = ev.data[1];
           zip[ev.data[0]] = ev.data[1];
         } else {
         } else {
+          console.log(zip);
           const buf = UZIP.encode(zip);
           const buf = UZIP.encode(zip);
-          wk.postMessage(new Uint8Array(buf), [buf]);
+          wk.postMessage([new Uint8Array(buf), true], [buf]);
         }
         }
       };
       };
     } else if (type == 'unzip') {
     } else if (type == 'unzip') {
@@ -115,7 +119,7 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
           outBufs.push(bufs[k]);
           outBufs.push(bufs[k]);
           bufs[k] = new Uint8Array(bufs[k]);
           bufs[k] = new Uint8Array(bufs[k]);
         }
         }
-        wk.postMessage(bufs, outBufs);
+        wk.postMessage([bufs, true], outBufs);
       }
       }
     } else {
     } else {
       const chunks: Uint8Array[] = [];
       const chunks: Uint8Array[] = [];
@@ -135,7 +139,7 @@ onmessage = (ev: MessageEvent<[string, string]>) => {
                     ? uzGzip(out)
                     ? uzGzip(out)
                     // we can pray that there's no special header
                     // we can pray that there's no special header
                     : UZIP.inflateRaw(out.subarray(10, -8));
                     : UZIP.inflateRaw(out.subarray(10, -8));
-          wk.postMessage(buf, [buf.buffer]);
+          wk.postMessage([buf, true], [buf.buffer]);
         }
         }
       }
       }
     }
     }

+ 0 - 5
yarn.lock

@@ -6508,11 +6508,6 @@ prelude-ls@~1.1.2:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
   integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
 
 
-prism-react-renderer@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44"
-  integrity sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug==
-
 process-nextick-args@~2.0.0:
 process-nextick-args@~2.0.0:
   version "2.0.1"
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"