Skip to content

Commit

Permalink
Compress shared example with gzip
Browse files Browse the repository at this point in the history
  • Loading branch information
weareoutman committed Sep 21, 2023
1 parent 2ff253a commit bc6402e
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 31 deletions.
36 changes: 26 additions & 10 deletions src/components/NextExample/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import clsx from "clsx";
import useDeferredValue from "@site/src/hooks/useDeferredValue";
import { EXAMPLE_IFRAME_MIN_HEIGHT } from "@site/src/constants";
import getContentHeightByCode from "@site/src/utils/getContentHeightByCode";
import { b64EncodeUnicode } from "@site/src/utils/b64Unicode";
import useExampleLanguage from "@site/src/hooks/useExampleLanguage";
import { GZIP_HASH_PREFIX, compress } from "@site/src/utils/gzip";
import ChevronUp from "./chevron-up.svg";
import ChevronDown from "./chevron-down.svg";
import styles from "./styles.module.css";
Expand Down Expand Up @@ -172,6 +172,30 @@ export default function NextExample({
setSourceShown((prev) => !prev);
}, []);

const [playgroundUrl, setPlaygroundUrl] = useState("/playground");

useEffect(() => {
let ignore = false;
async function updatePlaygroundUrl() {
try {
const url = `/playground${GZIP_HASH_PREFIX}${await compress(
JSON.stringify({
[language]: currentCode,
})
)}`;
if (!ignore) {
setPlaygroundUrl(url);
}
} catch (e) {
console.error("Compress shared example failed:", e);
}
}
updatePlaygroundUrl();
return () => {
ignore = true;
};
}, [currentCode, language]);

return (
<div className={styles.example} ref={containerRef}>
<div className={styles.previewBox}>
Expand Down Expand Up @@ -256,15 +280,7 @@ export default function NextExample({
>
YAML
</button>
<Link
className={styles.button}
to={`/playground#${b64EncodeUnicode(
JSON.stringify({
[language]: currentCode,
})
)}`}
target="_blank"
>
<Link className={styles.button} to={playgroundUrl} target="_blank">
Playground
<IconExternalLink width={12} height={12} />
</Link>
Expand Down
23 changes: 19 additions & 4 deletions src/pages/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import LoadingRing from "@site/src/components/LoadingRing";
import useDeferredValue from "@site/src/hooks/useDeferredValue";
import examplesJson from "@site/src/examples.json";
import usePlaygroundQuery from "@site/src/hooks/usePlaygroundQuery";
import { b64DecodeUnicode, b64EncodeUnicode } from "@site/src/utils/b64Unicode";
import { b64DecodeUnicode } from "@site/src/utils/b64Unicode";
import { decorateAltCode } from "@site/src/utils/decorateAltCode";
import { GZIP_HASH_PREFIX, compress, decompress } from "@site/src/utils/gzip";
import styles from "./style.module.css";

const { examples } = examplesJson;
Expand All @@ -34,6 +35,17 @@ const STORAGE_KEY_CODES = {
};
const SHARE_TEXT = "Share";

let decompressedExampleString: string;
if (location.hash.startsWith(GZIP_HASH_PREFIX)) {
try {
decompressedExampleString = await decompress(
location.hash.substring(GZIP_HASH_PREFIX.length)
);
} catch (e) {
console.error("Decompress shared example failed:", e);
}
}

export default function PlaygroundPage(): JSX.Element {
return (
<Layout title="Playground" description="Brick playground" noFooter>
Expand Down Expand Up @@ -176,11 +188,11 @@ function Playground(): JSX.Element {
[isLocal, mode]
);

const handleShare = useCallback(() => {
const handleShare = useCallback(async () => {
history.replaceState(
null,
"",
`#${b64EncodeUnicode(
`${GZIP_HASH_PREFIX}${await compress(
JSON.stringify({
[mode]: currentCode,
gap: hasGap,
Expand Down Expand Up @@ -357,7 +369,10 @@ function useInitialExample(): InitialExample {
if (hash) {
let sharedExample: InitialExample;
try {
sharedExample = JSON.parse(b64DecodeUnicode(location.hash.slice(1)));
sharedExample = JSON.parse(
decompressedExampleString ??
b64DecodeUnicode(location.hash.slice(1))
);
} catch (error) {
// eslint-disable-next-line no-console
console.error("Parse pasted sources failed:", error);
Expand Down
33 changes: 16 additions & 17 deletions src/utils/b64Unicode.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
export function b64EncodeUnicode(str: string) {
// first we use encodeURIComponent to get percent-encoded UTF-8,
// then we convert the percent encodings into raw bytes which
// can be fed into btoa.
return btoa(
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
return String.fromCharCode(parseInt(p1, 16));
})
);
// https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem

export function b64EncodeUnicode(str: string): string {
return bytesToBase64(new TextEncoder().encode(str));
}

export function b64DecodeUnicode(str: string): string {
return new TextDecoder().decode(base64ToBytes(str));
}

export function base64ToBytes(base64: string): Uint8Array {
const binString = atob(base64);
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}

export function b64DecodeUnicode(str: string) {
return decodeURIComponent(
[...atob(str)]
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
export function bytesToBase64(bytes: Uint8Array): string {
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join("");
return btoa(binString);
}
18 changes: 18 additions & 0 deletions src/utils/gzip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { base64ToBytes, bytesToBase64 } from "./b64Unicode";

export const GZIP_HASH_PREFIX = "#gzip,";

export async function compress(str: string) {
const blob = new Blob([str], { type: "text/plain" });
const stream = blob.stream().pipeThrough(new CompressionStream("gzip"));
const compressedBlob = await new Response(stream).blob();
return bytesToBase64(new Uint8Array(await compressedBlob.arrayBuffer()));
}

export async function decompress(str: string) {
const bytes = base64ToBytes(str);
const blob = new Blob([bytes], { type: "text/plain" });
const stream = blob.stream().pipeThrough(new DecompressionStream("gzip"));
const decompressedBlob = await new Response(stream).blob();
return decompressedBlob.text();
}

0 comments on commit bc6402e

Please sign in to comment.