summaryrefslogtreecommitdiff
path: root/demo/components/code-box
diff options
context:
space:
mode:
Diffstat (limited to 'demo/components/code-box')
-rw-r--r--demo/components/code-box/index.tsx540
-rw-r--r--demo/components/code-box/prism.css125
-rw-r--r--demo/components/code-box/prism.js505
-rw-r--r--demo/components/code-box/sandbox.ts150
-rw-r--r--demo/components/code-box/stream-adapter.tsx17
5 files changed, 1337 insertions, 0 deletions
diff --git a/demo/components/code-box/index.tsx b/demo/components/code-box/index.tsx
new file mode 100644
index 0000000..e5b5779
--- /dev/null
+++ b/demo/components/code-box/index.tsx
@@ -0,0 +1,540 @@
+import React, { FC, Ref, useEffect, useMemo, useRef, useState } from 'react';
+import { Prism } from './prism';
+import './prism';
+import './prism.css';
+import exec from './sandbox';
+
+const canStream = 'stream' in File.prototype;
+const rn = 'Running...';
+const wt = 'Waiting...';
+const tm = typeof performance != 'undefined'
+ ? () => performance.now()
+ : () => Date.now();
+
+type Preset = {
+ fflate: string;
+ uzip: string;
+ pako: string;
+};
+
+const presets: Record<string, Preset> = {
+ 'Basic GZIP compression': {
+ fflate: `var left = files.length;
+var filesLengths = {};
+
+// In a real app, use a list of file types to avoid compressing for better
+// performance
+var ALREADY_COMPRESSED = [
+ 'zip', 'gz', 'png', 'jpg', 'jpeg', 'pdf', 'doc', 'docx', 'ppt', 'pptx',
+ 'xls', 'xlsx', 'heic', 'heif', '7z', 'bz2', 'rar', 'gif', 'webp', 'webm',
+ 'mp4', 'mov', 'mp3', 'aifc'
+];
+
+// 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, {
+
+ // In a real app, instead of always compressing at a certain level,
+ // you'll want to check if the file is already compressed. For fairness,
+ // that's not done here.
+
+ /*
+ level: ALREADY_COMPRESSED.indexOf(
+ file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase()
+ ) == -1 ? 6 : 0
+ */
+
+ level: 6,
+
+ // You can uncomment the below for a contest of pure algorithm speed.
+ // In a real app, you'll probably not need to set the memory level
+ // because fflate picks a reasonable level based on file size by default.
+ // If fflate performs worse than UZIP, you're probably passing in
+ // incompressible files; switching the level or the mem will fix it.
+
+ /*
+ mem: 4
+ */
+
+ // The following are optional, but fflate supports metadata if you want
+ 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));
+ }
+ }
+ });
+ });
+}
+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) {
+
+ // 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.
+
+ pakoWorker.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);
+}`
+ },
+ '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',
+ 'mp4', 'mov', 'mp3', 'aifc'
+];
+
+// Yet again, this is necessary for parallelization.
+var processFile = function(i) {
+ var file = files[i];
+ var ext = file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase();
+ 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;
+
+// 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) {
+ presets['Streaming GZIP compression'] = {
+ 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 gzipStream = new AsyncGzip({ level: 6 });
+// We can stream the file through GZIP to reduce memory usage
+const gzipped = file.stream().pipeThrough(toNativeStream(gzipStream));
+let gzSize = 0;
+gzipped.pipeTo(new WritableStream({
+ write(chunk) {
+ gzSize += chunk.length;
+ },
+ close() {
+ callback('Length ' + gzSize);
+ }
+}));`,
+ 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...
+const file = files[0];
+// 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
+// code used in this demo, which is on GitHub under the demo/ directory.
+const gzipStream = pakoWorker.createGzip();
+const gzipped = file.stream().pipeThrough(toNativeStream(gzipStream));
+let gzSize = 0;
+gzipped.pipeTo(new WritableStream({
+ write(chunk) {
+ gzSize += chunk.length;
+ },
+ close() {
+ callback('Length ' + gzSize);
+ }
+}));`
+ };
+}
+
+const availablePresets = Object.keys(presets);
+
+const CodeHighlight: FC<{
+ code: string;
+ preset: string;
+ onInput: (newCode: string) => void;
+}> = ({ 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 (
+ <pre ref={pre} style={{
+ position: 'relative',
+ backgroundColor: '#2a2734',
+ color: '#9a86fd',
+ maxWidth: 'calc(90vw - 2em)',
+ fontSize: '0.7em',
+ marginTop: '1em',
+ marginBottom: '1em',
+ padding: '1em',
+ overflow: 'auto',
+ fontFamily: 'Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace'
+ }}>
+ <div dangerouslySetInnerHTML={highlight} />
+ <textarea
+ ref={ta}
+ autoComplete="off"
+ autoCorrect="off"
+ autoCapitalize="off"
+ spellCheck="false"
+ style={{
+ border: 0,
+ resize: 'none',
+ outline: 'none',
+ position: 'absolute',
+ background: 'transparent',
+ whiteSpace: 'pre',
+ top: 0,
+ left: 0,
+ width: 'calc(100% - 1em)',
+ height: 'calc(100% - 2em)',
+ overflow: 'hidden',
+ lineHeight: 'inherit',
+ fontSize: 'inherit',
+ padding: 'inherit',
+ paddingRight: 0,
+ color: 'transparent',
+ caretColor: 'white',
+ fontFamily: 'inherit'
+ }}
+ onKeyDown={e => {
+ const t = e.currentTarget;
+ let val = t.value;
+ const loc = t.selectionStart;
+ if (e.key == 'Enter') {
+ const lastNL = val.lastIndexOf('\n', loc - 1);
+ let indent = 0;
+ for (; val.charCodeAt(indent + lastNL + 1) == 32; ++indent);
+ const lastChar = val.charAt(loc - 1);
+ const nextChar = val.charAt(loc);
+ if (lastChar == '{'|| lastChar == '(' || lastChar == '[') indent += 2;
+ const addNL = nextChar == '}' || nextChar == ')' || nextChar == ']';
+ const tail = val.slice(t.selectionEnd);
+ val = val.slice(0, loc) + '\n';
+ for (let i = 0; i < indent; ++i) val += ' ';
+ if (addNL) {
+ if (
+ (lastChar == '{' && nextChar == '}') ||
+ (lastChar == '[' && nextChar == ']') ||
+ (lastChar == '(' && nextChar == ')')
+ ) {
+ val += '\n';
+ for (let i = 2; i < indent; ++i) val += ' ';
+ } else {
+ const end = Math.min(indent, 2);
+ indent -= end;
+ val = val.slice(0, -end);
+ }
+ }
+ t.value = val += tail;
+ t.selectionStart = t.selectionEnd = loc + indent + 1;
+ ta.current!.scrollLeft = 0;
+ } else if (e.key == 'Tab') {
+ t.value = val = val.slice(0, loc) + ' ' + val.slice(t.selectionEnd);
+ t.selectionStart = t.selectionEnd = loc + 2;
+ } else if (t.selectionStart == t.selectionEnd) {
+ if (e.key == 'Backspace') {
+ if (val.charCodeAt(loc - 1) == 32 && !val.slice(val.lastIndexOf('\n', loc - 1), loc).trim().length) {
+ t.value = val.slice(0, loc - 1) + val.slice(loc);
+ t.selectionStart = t.selectionEnd = loc - 1;
+ } else if (
+ (val.charAt(loc - 1) == '{' && val.charAt(loc) == '}') ||
+ (val.charAt(loc - 1) == '[' && val.charAt(loc) == ']') ||
+ (val.charAt(loc - 1) == '(' && val.charAt(loc) == ')')
+ ) {
+ t.value = val.slice(0, loc) + val.slice(loc + 1);
+ // hack, doesn't work always
+ t.selectionStart = t.selectionEnd = loc;
+ }
+ return;
+ } else {
+ switch(e.key) {
+ case '{':
+ case '[':
+ case '(':
+ t.value = val = val.slice(0, loc) + (e.key == '{' ? '}' : e.key == '[' ? ']' : ')') + val.slice(loc);
+ t.selectionStart = t.selectionEnd = loc;
+ break;
+ case '}':
+ case ']':
+ case ')':
+ // BUG: if the cursor is moved, this false activates
+ 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 sl = val.slice(lastNL, 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;
+ }
+ }
+ return;
+ };
+ } else return;
+ e.preventDefault();
+ onInput(val);
+ }}
+ onInput={e => onInput(e.currentTarget.value)}
+ >
+ {code}
+ </textarea>
+ </pre>
+ )
+};
+
+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 codes: Preset = {
+ fflate,
+ uzip,
+ pako
+ };
+ codes[lib] = code;
+ setCodes(codes);
+ setPreset('Custom');
+ }
+ const [hover, setHover] = useState(false);
+ return (
+ <div ref={forwardRef} style={{
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'space-between',
+ 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={{
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-around',
+ whiteSpace: 'pre-wrap',
+ textAlign: 'left',
+ flexWrap: 'wrap'
+ }}>
+ <div style={{ padding: '2vmin' }}>
+ fflate:
+ <CodeHighlight code={fflate} preset={preset} onInput={t => onInput('fflate', t)} />
+ <span dangerouslySetInnerHTML={{ __html: ffl }} />
+ </div>
+ <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>
+ <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 => {
+ const tf = tm();
+ setFFL('Finished in <span style="font-weight:bold">' + (tf - ts).toFixed(3) + 'ms</span>: ' + out);
+ exec(uzip, files, out => {
+ const tu = tm();
+ setUZ('Finished in <span style="font-weight:bold">' + (tu - tf).toFixed(3) + 'ms:</span> ' + out);
+ exec(pako, files, out => {
+ setPK('Finished in <span style="font-weight:bold">' + (tm() - tu).toFixed(3) + 'ms:</span> ' + out);
+ });
+ });
+ });
+ }}>Run</button>
+ </div>
+ );
+}
+
+export default CodeBox; \ No newline at end of file
diff --git a/demo/components/code-box/prism.css b/demo/components/code-box/prism.css
new file mode 100644
index 0000000..0bfe998
--- /dev/null
+++ b/demo/components/code-box/prism.css
@@ -0,0 +1,125 @@
+/* PrismJS 1.22.0
+https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+javascript */
+/**
+ * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
+ * Based on https://github.com/chriskempson/tomorrow-theme
+ * @author Rose Pritchard
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+ color: #ccc;
+ background: none;
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+ font-size: 1em;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
+
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+ padding: 1em;
+ margin: .5em 0;
+ overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+ background: #2d2d2d;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+ padding: .1em;
+ border-radius: .3em;
+ white-space: normal;
+}
+
+.token.comment,
+.token.block-comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #999;
+}
+
+.token.punctuation {
+ color: #ccc;
+}
+
+.token.tag,
+.token.attr-name,
+.token.namespace,
+.token.deleted {
+ color: #e2777a;
+}
+
+.token.function-name {
+ color: #6196cc;
+}
+
+.token.boolean,
+.token.number,
+.token.function {
+ color: #f08d49;
+}
+
+.token.property,
+.token.class-name,
+.token.constant,
+.token.symbol {
+ color: #f8c555;
+}
+
+.token.selector,
+.token.important,
+.token.atrule,
+.token.keyword,
+.token.builtin {
+ color: #cc99cd;
+}
+
+.token.string,
+.token.char,
+.token.attr-value,
+.token.regex,
+.token.variable {
+ color: #7ec699;
+}
+
+.token.operator,
+.token.entity,
+.token.url {
+ color: #67cdcc;
+}
+
+.token.important,
+.token.bold {
+ font-weight: bold;
+}
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
+
+.token.inserted {
+ color: green;
+}
+
diff --git a/demo/components/code-box/prism.js b/demo/components/code-box/prism.js
new file mode 100644
index 0000000..e21620f
--- /dev/null
+++ b/demo/components/code-box/prism.js
@@ -0,0 +1,505 @@
+/* PrismJS 1.22.0
+https://prismjs.com/download.html#themes=prism-tomorrow&languages=clike+javascript */
+var Prism = (function (u) {
+ var c = /\blang(?:uage)?-([\w-]+)\b/i,
+ n = 0,
+ M = {
+ manual: u.Prism && u.Prism.manual,
+ disableWorkerMessageHandler:
+ u.Prism && u.Prism.disableWorkerMessageHandler,
+ util: {
+ encode: function e(n) {
+ return n instanceof W
+ ? new W(n.type, e(n.content), n.alias)
+ : Array.isArray(n)
+ ? n.map(e)
+ : n
+ .replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/\u00a0/g, " ");
+ },
+ type: function (e) {
+ return Object.prototype.toString.call(e).slice(8, -1);
+ },
+ objId: function (e) {
+ return (
+ e.__id || Object.defineProperty(e, "__id", { value: ++n }), e.__id
+ );
+ },
+ clone: function t(e, r) {
+ var a, n;
+ switch (((r = r || {}), M.util.type(e))) {
+ case "Object":
+ if (((n = M.util.objId(e)), r[n])) return r[n];
+ for (var i in ((a = {}), (r[n] = a), e))
+ e.hasOwnProperty(i) && (a[i] = t(e[i], r));
+ return a;
+ case "Array":
+ return (
+ (n = M.util.objId(e)),
+ r[n]
+ ? r[n]
+ : ((a = []),
+ (r[n] = a),
+ e.forEach(function (e, n) {
+ a[n] = t(e, r);
+ }),
+ a)
+ );
+ default:
+ return e;
+ }
+ },
+ getLanguage: function (e) {
+ for (; e && !c.test(e.className); ) e = e.parentElement;
+ return e
+ ? (e.className.match(c) || [, "none"])[1].toLowerCase()
+ : "none";
+ },
+ currentScript: function () {
+ if ("undefined" == typeof document) return null;
+ if ("currentScript" in document) return document.currentScript;
+ try {
+ throw new Error();
+ } catch (e) {
+ var n = (/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack) || [])[1];
+ if (n) {
+ var t = document.getElementsByTagName("script");
+ for (var r in t) if (t[r].src == n) return t[r];
+ }
+ return null;
+ }
+ },
+ isActive: function (e, n, t) {
+ for (var r = "no-" + n; e; ) {
+ var a = e.classList;
+ if (a.contains(n)) return !0;
+ if (a.contains(r)) return !1;
+ e = e.parentElement;
+ }
+ return !!t;
+ },
+ },
+ languages: {
+ extend: function (e, n) {
+ var t = M.util.clone(M.languages[e]);
+ for (var r in n) t[r] = n[r];
+ return t;
+ },
+ insertBefore: function (t, e, n, r) {
+ var a = (r = r || M.languages)[t],
+ i = {};
+ for (var l in a)
+ if (a.hasOwnProperty(l)) {
+ if (l == e) for (var o in n) n.hasOwnProperty(o) && (i[o] = n[o]);
+ n.hasOwnProperty(l) || (i[l] = a[l]);
+ }
+ var s = r[t];
+ return (
+ (r[t] = i),
+ M.languages.DFS(M.languages, function (e, n) {
+ n === s && e != t && (this[e] = i);
+ }),
+ i
+ );
+ },
+ DFS: function e(n, t, r, a) {
+ a = a || {};
+ var i = M.util.objId;
+ for (var l in n)
+ if (n.hasOwnProperty(l)) {
+ t.call(n, l, n[l], r || l);
+ var o = n[l],
+ s = M.util.type(o);
+ "Object" !== s || a[i(o)]
+ ? "Array" !== s || a[i(o)] || ((a[i(o)] = !0), e(o, t, l, a))
+ : ((a[i(o)] = !0), e(o, t, null, a));
+ }
+ },
+ },
+ plugins: {},
+ highlightAll: function (e, n) {
+ M.highlightAllUnder(document, e, n);
+ },
+ highlightAllUnder: function (e, n, t) {
+ var r = {
+ callback: t,
+ container: e,
+ selector:
+ 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
+ };
+ M.hooks.run("before-highlightall", r),
+ (r.elements = Array.prototype.slice.apply(
+ r.container.querySelectorAll(r.selector)
+ )),
+ M.hooks.run("before-all-elements-highlight", r);
+ for (var a, i = 0; (a = r.elements[i++]); )
+ M.highlightElement(a, !0 === n, r.callback);
+ },
+ highlightElement: function (e, n, t) {
+ var r = M.util.getLanguage(e),
+ a = M.languages[r];
+ e.className =
+ e.className.replace(c, "").replace(/\s+/g, " ") + " language-" + r;
+ var i = e.parentElement;
+ i &&
+ "pre" === i.nodeName.toLowerCase() &&
+ (i.className =
+ i.className.replace(c, "").replace(/\s+/g, " ") + " language-" + r);
+ var l = { element: e, language: r, grammar: a, code: e.textContent };
+ function o(e) {
+ (l.highlightedCode = e),
+ M.hooks.run("before-insert", l),
+ (l.element.innerHTML = l.highlightedCode),
+ M.hooks.run("after-highlight", l),
+ M.hooks.run("complete", l),
+ t && t.call(l.element);
+ }
+ if ((M.hooks.run("before-sanity-check", l), !l.code))
+ return M.hooks.run("complete", l), void (t && t.call(l.element));
+ if ((M.hooks.run("before-highlight", l), l.grammar))
+ if (n && u.Worker) {
+ var s = new Worker(M.filename);
+ (s.onmessage = function (e) {
+ o(e.data);
+ }),
+ s.postMessage(
+ JSON.stringify({
+ language: l.language,
+ code: l.code,
+ immediateClose: !0,
+ })
+ );
+ } else o(M.highlight(l.code, l.grammar, l.language));
+ else o(M.util.encode(l.code));
+ },
+ highlight: function (e, n, t) {
+ var r = { code: e, grammar: n, language: t };
+ return (
+ M.hooks.run("before-tokenize", r),
+ (r.tokens = M.tokenize(r.code, r.grammar)),
+ M.hooks.run("after-tokenize", r),
+ W.stringify(M.util.encode(r.tokens), r.language)
+ );
+ },
+ tokenize: function (e, n) {
+ var t = n.rest;
+ if (t) {
+ for (var r in t) n[r] = t[r];
+ delete n.rest;
+ }
+ var a = new i();
+ return (
+ I(a, a.head, e),
+ (function e(n, t, r, a, i, l) {
+ for (var o in r)
+ if (r.hasOwnProperty(o) && r[o]) {
+ var s = r[o];
+ s = Array.isArray(s) ? s : [s];
+ for (var u = 0; u < s.length; ++u) {
+ if (l && l.cause == o + "," + u) return;
+ var c = s[u],
+ g = c.inside,
+ f = !!c.lookbehind,
+ h = !!c.greedy,
+ d = 0,
+ v = c.alias;
+ if (h && !c.pattern.global) {
+ var p = c.pattern.toString().match(/[imsuy]*$/)[0];
+ c.pattern = RegExp(c.pattern.source, p + "g");
+ }
+ for (
+ var m = c.pattern || c, y = a.next, k = i;
+ y !== t.tail && !(l && k >= l.reach);
+ k += y.value.length, y = y.next
+ ) {
+ var b = y.value;
+ if (t.length > n.length) return;
+ if (!(b instanceof W)) {
+ var x = 1;
+ if (h && y != t.tail.prev) {
+ m.lastIndex = k;
+ var w = m.exec(n);
+ if (!w) break;
+ var A = w.index + (f && w[1] ? w[1].length : 0),
+ P = w.index + w[0].length,
+ S = k;
+ for (S += y.value.length; S <= A; )
+ (y = y.next), (S += y.value.length);
+ if (
+ ((S -= y.value.length), (k = S), y.value instanceof W)
+ )
+ continue;
+ for (
+ var E = y;
+ E !== t.tail && (S < P || "string" == typeof E.value);
+ E = E.next
+ )
+ x++, (S += E.value.length);
+ x--, (b = n.slice(k, S)), (w.index -= k);
+ } else {
+ m.lastIndex = 0;
+ var w = m.exec(b);
+ }
+ if (w) {
+ f && (d = w[1] ? w[1].length : 0);
+ var A = w.index + d,
+ O = w[0].slice(d),
+ P = A + O.length,
+ L = b.slice(0, A),
+ N = b.slice(P),
+ j = k + b.length;
+ l && j > l.reach && (l.reach = j);
+ var C = y.prev;
+ L && ((C = I(t, C, L)), (k += L.length)), z(t, C, x);
+ var _ = new W(o, g ? M.tokenize(O, g) : O, v, O);
+ (y = I(t, C, _)),
+ N && I(t, y, N),
+ 1 < x &&
+ e(n, t, r, y.prev, k, {
+ cause: o + "," + u,
+ reach: j,
+ });
+ }
+ }
+ }
+ }
+ }
+ })(e, a, n, a.head, 0),
+ (function (e) {
+ var n = [],
+ t = e.head.next;
+ for (; t !== e.tail; ) n.push(t.value), (t = t.next);
+ return n;
+ })(a)
+ );
+ },
+ hooks: {
+ all: {},
+ add: function (e, n) {
+ var t = M.hooks.all;
+ (t[e] = t[e] || []), t[e].push(n);
+ },
+ run: function (e, n) {
+ var t = M.hooks.all[e];
+ if (t && t.length) for (var r, a = 0; (r = t[a++]); ) r(n);
+ },
+ },
+ Token: W,
+ };
+ function W(e, n, t, r) {
+ (this.type = e),
+ (this.content = n),
+ (this.alias = t),
+ (this.length = 0 | (r || "").length);
+ }
+ function i() {
+ var e = { value: null, prev: null, next: null },
+ n = { value: null, prev: e, next: null };
+ (e.next = n), (this.head = e), (this.tail = n), (this.length = 0);
+ }
+ function I(e, n, t) {
+ var r = n.next,
+ a = { value: t, prev: n, next: r };
+ return (n.next = a), (r.prev = a), e.length++, a;
+ }
+ function z(e, n, t) {
+ for (var r = n.next, a = 0; a < t && r !== e.tail; a++) r = r.next;
+ ((n.next = r).prev = n), (e.length -= a);
+ }
+ if (
+ ((u.Prism = M),
+ (W.stringify = function n(e, t) {
+ if ("string" == typeof e) return e;
+ if (Array.isArray(e)) {
+ var r = "";
+ return (
+ e.forEach(function (e) {
+ r += n(e, t);
+ }),
+ r
+ );
+ }
+ var a = {
+ type: e.type,
+ content: n(e.content, t),
+ tag: "span",
+ classes: ["token", e.type],
+ attributes: {},
+ language: t,
+ },
+ i = e.alias;
+ i &&
+ (Array.isArray(i)
+ ? Array.prototype.push.apply(a.classes, i)
+ : a.classes.push(i)),
+ M.hooks.run("wrap", a);
+ var l = "";
+ for (var o in a.attributes)
+ l +=
+ " " +
+ o +
+ '="' +
+ (a.attributes[o] || "").replace(/"/g, "&quot;") +
+ '"';
+ return (
+ "<" +
+ a.tag +
+ ' class="' +
+ a.classes.join(" ") +
+ '"' +
+ l +
+ ">" +
+ a.content +
+ "</" +
+ a.tag +
+ ">"
+ );
+ }),
+ !u.document)
+ )
+ return (
+ u.addEventListener &&
+ (M.disableWorkerMessageHandler ||
+ u.addEventListener(
+ "message",
+ function (e) {
+ var n = JSON.parse(e.data),
+ t = n.language,
+ r = n.code,
+ a = n.immediateClose;
+ u.postMessage(M.highlight(r, M.languages[t], t)), a && u.close();
+ },
+ !1
+ )),
+ M
+ );
+ var e = M.util.currentScript();
+ function t() {
+ M.manual || M.highlightAll();
+ }
+ if (
+ (e &&
+ ((M.filename = e.src), e.hasAttribute("data-manual") && (M.manual = !0)),
+ !M.manual)
+ ) {
+ var r = document.readyState;
+ "loading" === r || ("interactive" === r && e && e.defer)
+ ? document.addEventListener("DOMContentLoaded", t)
+ : window.requestAnimationFrame
+ ? window.requestAnimationFrame(t)
+ : window.setTimeout(t, 16);
+ }
+ return M;
+})(module.exports);
+Prism.languages.clike = {
+ comment: [
+ { pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0 },
+ { pattern: /(^|[^\\:])\/\/.*/, lookbehind: !0, greedy: !0 },
+ ],
+ string: {
+ pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
+ greedy: !0,
+ },
+ "class-name": {
+ pattern: /(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,
+ lookbehind: !0,
+ inside: { punctuation: /[.\\]/ },
+ },
+ keyword: /\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,
+ boolean: /\b(?:true|false)\b/,
+ function: /\w+(?=\()/,
+ number: /\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,
+ operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
+ punctuation: /[{}[\];(),.:]/,
+};
+(Prism.languages.javascript = Prism.languages.extend("clike", {
+ "class-name": [
+ Prism.languages.clike["class-name"],
+ {
+ pattern: /(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,
+ lookbehind: !0,
+ },
+ ],
+ keyword: [
+ { pattern: /((?:^|})\s*)(?:catch|finally)\b/, lookbehind: !0 },
+ {
+ pattern: /(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,
+ lookbehind: !0,
+ },
+ ],
+ number: /\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,
+ function: /#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,
+ operator: /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/,
+})),
+ (Prism.languages.javascript[
+ "class-name"
+ ][0].pattern = /(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/),
+ Prism.languages.insertBefore("javascript", "keyword", {
+ regex: {
+ pattern: /((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,
+ lookbehind: !0,
+ greedy: !0,
+ inside: {
+ "regex-source": {
+ pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/,
+ lookbehind: !0,
+ alias: "language-regex",
+ inside: Prism.languages.regex,
+ },
+ "regex-flags": /[a-z]+$/,
+ "regex-delimiter": /^\/|\/$/,
+ },
+ },
+ "function-variable": {
+ pattern: /#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,
+ alias: "function",
+ },
+ parameter: [
+ {
+ pattern: /(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,
+ lookbehind: !0,
+ inside: Prism.languages.javascript,
+ },
+ {
+ pattern: /[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,
+ inside: Prism.languages.javascript,
+ },
+ {
+ pattern: /(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,
+ lookbehind: !0,
+ inside: Prism.languages.javascript,
+ },
+ {
+ pattern: /((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,
+ lookbehind: !0,
+ inside: Prism.languages.javascript,
+ },
+ ],
+ constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/,
+ }),
+ Prism.languages.insertBefore("javascript", "string", {
+ "template-string": {
+ pattern: /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,
+ greedy: !0,
+ inside: {
+ "template-punctuation": { pattern: /^`|`$/, alias: "string" },
+ interpolation: {
+ pattern: /((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,
+ lookbehind: !0,
+ inside: {
+ "interpolation-punctuation": {
+ pattern: /^\${|}$/,
+ alias: "punctuation",
+ },
+ rest: Prism.languages.javascript,
+ },
+ },
+ string: /[\s\S]+/,
+ },
+ },
+ }),
+ Prism.languages.markup &&
+ Prism.languages.markup.tag.addInlined("script", "javascript"),
+ (Prism.languages.js = Prism.languages.javascript);
+module.exports.Prism = Prism; \ No newline at end of file
diff --git a/demo/components/code-box/sandbox.ts b/demo/components/code-box/sandbox.ts
new file mode 100644
index 0000000..b03d2ce
--- /dev/null
+++ b/demo/components/code-box/sandbox.ts
@@ -0,0 +1,150 @@
+import * as fflate from '../../..';
+import toNativeStream from './stream-adapter';
+
+type Callback = (...args: unknown[]) => void;
+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 p: WorkerProxy = {};
+ for (const k of keys) {
+ const base = function(cb: (...args: unknown[]) => void) {
+ const w = new Worker('../../util/workers.ts');
+ w.postMessage([lib, k]);
+ w.onmessage = function(msg) {
+ const args = msg.data;
+ args.unshift(null);
+ cb.apply(null, args);
+ }
+ w.onerror = err => cb(err);
+ return w;
+ }
+ if (k != 'zip' && k != 'unzip') {
+ p[k] = function(dat, cb) {
+ 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]);
+ }
+ p['create' + k.slice(0, 1).toUpperCase() + k.slice(1)] = function() {
+ let trueCb = arguments[0];
+ const w = base((err, dat, final) => {
+ trueCb(err, dat, final);
+ });
+ const out = {
+ ondata: trueCb,
+ push(v: Uint8Array, f: boolean) {
+ if (!out.ondata) throw 'no callback';
+ trueCb = out.ondata;
+ w.postMessage([v, f], [v.buffer]);
+ },
+ terminate() {
+ w.terminate();
+ }
+ }
+ return out;
+ }
+ } else {
+ p[k] = function() {
+ 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);
+ }
+ }
+ return out;
+ }
+ }
+ }
+ return p;
+}
+
+const keys = ['zip', 'unzip', 'deflate', 'inflate', 'gzip', 'gunzip', 'zlib', 'unzlib'];
+
+const uzipWorker = createWorkerProxy('uzip', keys);
+const pakoWorker = createWorkerProxy('pako', keys);
+const fileToU8 = (file: File, cb: (out: Uint8Array) => void) => {
+ const fr = new FileReader();
+ fr.onloadend = () => {
+ cb(new Uint8Array(fr.result as ArrayBuffer));
+ }
+ fr.readAsArrayBuffer(file);
+};
+
+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 = {
+ fflate,
+ uzipWorker,
+ pakoWorker,
+ toNativeStream,
+ callback,
+ fileToU8,
+ files,
+ download,
+ prettySizes
+ };
+ 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; \ No newline at end of file
diff --git a/demo/components/code-box/stream-adapter.tsx b/demo/components/code-box/stream-adapter.tsx
new file mode 100644
index 0000000..a97d2ed
--- /dev/null
+++ b/demo/components/code-box/stream-adapter.tsx
@@ -0,0 +1,17 @@
+import { AsyncDeflate } from '../../..';
+export default (stream: AsyncDeflate) => {
+ const writable = new WritableStream({
+ write(dat: Uint8Array) { stream.push(dat); },
+ close() { stream.push(new Uint8Array(0), true); }
+ });
+ const readable = new ReadableStream({
+ start(controller: ReadableStreamDefaultController<Uint8Array>) {
+ stream.ondata = (err, chunk, final) => {
+ if (err) writable.abort(err.message);
+ controller.enqueue(chunk);
+ if (final) controller.close();
+ }
+ }
+ });
+ return { readable, writable };
+} \ No newline at end of file