|
@@ -4,7 +4,6 @@ import Prism from './prism';
|
|
|
import './prism';
|
|
|
import './prism.css';
|
|
|
import exec from './sandbox';
|
|
|
-import ts from 'typescript';
|
|
|
|
|
|
const canStream = 'stream' in File.prototype;
|
|
|
|
|
@@ -16,8 +15,7 @@ type Preset = {
|
|
|
|
|
|
const presets: Record<string, Preset> = {
|
|
|
'Basic GZIP compression': {
|
|
|
- fflate: `
|
|
|
-var file = files[0];
|
|
|
+ fflate: `var file = files[0];
|
|
|
fileToU8(file, function(buf) {
|
|
|
fflate.gzip(buf, {
|
|
|
level: 6,
|
|
@@ -29,8 +27,7 @@ fileToU8(file, function(buf) {
|
|
|
callback(data);
|
|
|
});
|
|
|
});`,
|
|
|
- uzip: `
|
|
|
-var file = files[0];
|
|
|
+ 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.
|
|
@@ -43,8 +40,7 @@ fileToU8(file, function(buf) {
|
|
|
callback(data);
|
|
|
});
|
|
|
});`,
|
|
|
- pako: `
|
|
|
-var file = files[0];
|
|
|
+ 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
|
|
@@ -60,8 +56,7 @@ fileToU8(file, function(buf) {
|
|
|
|
|
|
if (canStream) {
|
|
|
presets['Streaming GZIP compression'] = {
|
|
|
- fflate: `
|
|
|
-const { AsyncGzip } = fflate;
|
|
|
+ fflate: `const { AsyncGzip } = fflate;
|
|
|
const file = files[0];
|
|
|
const gzipStream = new AsyncGzip({ level: 6 });
|
|
|
// We can stream the file through GZIP to reduce memory usage
|
|
@@ -71,10 +66,8 @@ const fakeResponse = new Response(
|
|
|
fakeResponse.arrayBuffer().then(buf => {
|
|
|
callback(new Uint8Array(buf));
|
|
|
});`,
|
|
|
- uzip: `
|
|
|
-// UZIP doesn't support streaming to any extent`,
|
|
|
- pako: `
|
|
|
-// Hundreds of lines of code to make this run on a Worker...
|
|
|
+ uzip: `// UZIP doesn't support streaming to any extent`,
|
|
|
+ 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
|
|
@@ -95,139 +88,136 @@ const CodeHighlight: FC<{
|
|
|
}> = ({ code, onInput }) => {
|
|
|
const tmpParen = useRef(-1);
|
|
|
return (
|
|
|
- <>
|
|
|
- <pre className="language-javascript" style={{
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- position: 'relative',
|
|
|
- backgroundColor: '#2a2734',
|
|
|
- color: '#9a86fd',
|
|
|
- fontSize: '0.7em'
|
|
|
- }}>
|
|
|
- <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) => (
|
|
|
- <span {...getTokenProps({ token, key })} />
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- ))
|
|
|
- )}
|
|
|
- </Highlight>
|
|
|
- </div>
|
|
|
- <textarea
|
|
|
- autoComplete="off"
|
|
|
- autoCorrect="off"
|
|
|
- autoCapitalize="off"
|
|
|
- spellCheck="false"
|
|
|
- style={{
|
|
|
- border: 'unset',
|
|
|
- resize: 'none',
|
|
|
- outline: 'none',
|
|
|
- position: 'absolute',
|
|
|
- background: 'transparent',
|
|
|
- top: 0,
|
|
|
- left: 0,
|
|
|
- width: '100%',
|
|
|
- height: '100%',
|
|
|
- lineHeight: 'inherit',
|
|
|
- fontSize: 'inherit',
|
|
|
- padding: 'inherit',
|
|
|
- color: 'transparent',
|
|
|
- caretColor: 'white',
|
|
|
- fontFamily: 'inherit'
|
|
|
- }}
|
|
|
- onKeyDown={e => {
|
|
|
- const t = e.currentTarget;
|
|
|
- let val = t.value;
|
|
|
- const loc = t.selectionStart;
|
|
|
- let newTmpParen = -1;
|
|
|
- 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);
|
|
|
- }
|
|
|
- }
|
|
|
- val += tail;
|
|
|
- t.value = val;
|
|
|
- t.selectionStart = t.selectionEnd = loc + indent + 1;
|
|
|
- } else if (e.key == 'Tab') {
|
|
|
- val = val.slice(0, loc) + ' ' + val.slice(t.selectionEnd);
|
|
|
- t.value = val;
|
|
|
- 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) {
|
|
|
- val = val.slice(0, loc - 2) + val.slice(loc);
|
|
|
- t.value = val;
|
|
|
- t.selectionStart = t.selectionEnd = loc - 2;
|
|
|
- } else if (
|
|
|
- (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;
|
|
|
+ <pre className="language-javascript" style={{
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ position: 'relative',
|
|
|
+ backgroundColor: '#2a2734',
|
|
|
+ color: '#9a86fd',
|
|
|
+ fontSize: '0.7em'
|
|
|
+ }}>
|
|
|
+ <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>
|
|
|
+ <textarea
|
|
|
+ autoComplete="off"
|
|
|
+ autoCorrect="off"
|
|
|
+ autoCapitalize="off"
|
|
|
+ spellCheck="false"
|
|
|
+ style={{
|
|
|
+ border: 'unset',
|
|
|
+ resize: 'none',
|
|
|
+ outline: 'none',
|
|
|
+ position: 'absolute',
|
|
|
+ background: 'transparent',
|
|
|
+ top: 0,
|
|
|
+ left: 0,
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ lineHeight: 'inherit',
|
|
|
+ fontSize: 'inherit',
|
|
|
+ padding: 'inherit',
|
|
|
+ color: 'transparent',
|
|
|
+ caretColor: 'white',
|
|
|
+ fontFamily: 'inherit'
|
|
|
+ }}
|
|
|
+ onKeyDown={e => {
|
|
|
+ const t = e.currentTarget;
|
|
|
+ let val = t.value;
|
|
|
+ const loc = t.selectionStart;
|
|
|
+ let newTmpParen = -1;
|
|
|
+ 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 {
|
|
|
- let a: string;
|
|
|
- switch(e.key) {
|
|
|
- case '{':
|
|
|
- case '[':
|
|
|
- case '(':
|
|
|
- t.value = val = val.slice(0, loc) + (e.key == '{' ? '{}' : e.key == '[' ? '[]' : '()') + val.slice(loc);
|
|
|
- t.selectionStart = t.selectionEnd = newTmpParen = loc + 1;
|
|
|
- break;
|
|
|
- case '}':
|
|
|
- case ']':
|
|
|
- case ')':
|
|
|
- // BUG: if the cursor is moved, this false activates
|
|
|
- if (tmpParen.current != loc) {
|
|
|
- const lastNL = val.lastIndexOf('\n', loc - 1);
|
|
|
- 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);
|
|
|
- }
|
|
|
- t.selectionEnd = t.selectionStart = loc + 1;
|
|
|
- break;
|
|
|
- default:
|
|
|
- tmpParen.current = -1;
|
|
|
- return;
|
|
|
- }
|
|
|
- };
|
|
|
- } else return;
|
|
|
- tmpParen.current = newTmpParen;
|
|
|
- e.preventDefault();
|
|
|
- onInput(val);
|
|
|
- }}
|
|
|
- onInput={e => onInput(e.currentTarget.value)}
|
|
|
- >
|
|
|
- {code}
|
|
|
- </textarea>
|
|
|
- </pre>
|
|
|
- </>
|
|
|
-
|
|
|
+ const end = Math.min(indent, 2);
|
|
|
+ indent -= end;
|
|
|
+ val = val.slice(0, -end);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ val += tail;
|
|
|
+ t.value = val;
|
|
|
+ t.selectionStart = t.selectionEnd = loc + indent + 1;
|
|
|
+ } else if (e.key == 'Tab') {
|
|
|
+ val = val.slice(0, loc) + ' ' + val.slice(t.selectionEnd);
|
|
|
+ t.value = val;
|
|
|
+ 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) {
|
|
|
+ val = val.slice(0, loc - 2) + val.slice(loc);
|
|
|
+ t.value = val;
|
|
|
+ t.selectionStart = t.selectionEnd = loc - 2;
|
|
|
+ } else if (
|
|
|
+ (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;
|
|
|
+ } else {
|
|
|
+ let a: string;
|
|
|
+ switch(e.key) {
|
|
|
+ case '{':
|
|
|
+ case '[':
|
|
|
+ case '(':
|
|
|
+ t.value = val = val.slice(0, loc) + (e.key == '{' ? '{}' : e.key == '[' ? '[]' : '()') + val.slice(loc);
|
|
|
+ t.selectionStart = t.selectionEnd = newTmpParen = loc + 1;
|
|
|
+ break;
|
|
|
+ case '}':
|
|
|
+ case ']':
|
|
|
+ case ')':
|
|
|
+ // BUG: if the cursor is moved, this false activates
|
|
|
+ if (tmpParen.current != loc) {
|
|
|
+ const lastNL = val.lastIndexOf('\n', loc - 1);
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ t.selectionEnd = t.selectionStart = loc + 1;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ tmpParen.current = -1;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ } else return;
|
|
|
+ tmpParen.current = newTmpParen;
|
|
|
+ e.preventDefault();
|
|
|
+ onInput(val);
|
|
|
+ }}
|
|
|
+ onInput={e => onInput(e.currentTarget.value)}
|
|
|
+ >
|
|
|
+ {code}
|
|
|
+ </textarea>
|
|
|
+ </pre>
|
|
|
)
|
|
|
};
|
|
|
|
|
@@ -258,15 +248,15 @@ const CodeBox: FC<{files: File[]}> = ({ files }) => {
|
|
|
}}>
|
|
|
<div>
|
|
|
fflate:
|
|
|
- <CodeHighlight code={fflate.trim()} onInput={t => onInput('fflate', t)} />
|
|
|
+ <CodeHighlight code={fflate} onInput={t => onInput('fflate', t)} />
|
|
|
</div>
|
|
|
<div>
|
|
|
UZIP (shimmed):
|
|
|
- <CodeHighlight code={uzip.trim()} onInput={t => onInput('uzip', t)} />
|
|
|
+ <CodeHighlight code={uzip} onInput={t => onInput('uzip', t)} />
|
|
|
</div>
|
|
|
<div>
|
|
|
Pako (shimmed):
|
|
|
- <CodeHighlight code={pako.trim()} onInput={t => onInput('pako', t)} />
|
|
|
+ <CodeHighlight code={pako} onInput={t => onInput('pako', t)} />
|
|
|
</div>
|
|
|
</div>
|
|
|
<button onClick={() => {
|